clawt 2.7.4 → 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.
@@ -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
@@ -221,6 +221,9 @@ var CONFIG_DESCRIPTIONS = deriveConfigDescriptions(CONFIG_DEFINITIONS);
221
221
  // src/constants/git.ts
222
222
  var AUTO_SAVE_COMMIT_MESSAGE = "chore: auto-save before sync";
223
223
 
224
+ // src/constants/logger.ts
225
+ var DEBUG_TIMESTAMP_FORMAT = "HH:mm:ss.SSS";
226
+
224
227
  // src/errors/index.ts
225
228
  var ClawtError = class extends Error {
226
229
  /** 退出码 */
@@ -239,6 +242,7 @@ var ClawtError = class extends Error {
239
242
  // src/logger/index.ts
240
243
  import winston from "winston";
241
244
  import DailyRotateFile from "winston-daily-rotate-file";
245
+ import chalk from "chalk";
242
246
  import { existsSync, mkdirSync } from "fs";
243
247
  if (!existsSync(LOGS_DIR)) {
244
248
  mkdirSync(LOGS_DIR, { recursive: true });
@@ -262,6 +266,35 @@ var logger = winston.createLogger({
262
266
  ),
263
267
  transports: [dailyRotateTransport]
264
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
+ }
265
298
 
266
299
  // src/utils/shell.ts
267
300
  import { execSync, execFileSync, spawn } from "child_process";
@@ -467,16 +500,16 @@ function gitApplyCachedCheck(patchContent, cwd) {
467
500
  }
468
501
 
469
502
  // src/utils/formatter.ts
470
- import chalk from "chalk";
503
+ import chalk2 from "chalk";
471
504
  import { createInterface } from "readline";
472
505
  function printSuccess(message) {
473
- console.log(chalk.green(message));
506
+ console.log(chalk2.green(message));
474
507
  }
475
508
  function printError(message) {
476
- console.error(chalk.red(`\u2717 ${message}`));
509
+ console.error(chalk2.red(`\u2717 ${message}`));
477
510
  }
478
511
  function printWarning(message) {
479
- console.log(chalk.yellow(`\u26A0 ${message}`));
512
+ console.log(chalk2.yellow(`\u26A0 ${message}`));
480
513
  }
481
514
  function printInfo(message) {
482
515
  console.log(message);
@@ -500,7 +533,7 @@ function confirmAction(question) {
500
533
  });
501
534
  }
502
535
  function confirmDestructiveAction(dangerousCommand, description) {
503
- printWarning(`\u5373\u5C06\u6267\u884C ${chalk.red.bold(dangerousCommand)}\uFF0C${description}`);
536
+ printWarning(`\u5373\u5C06\u6267\u884C ${chalk2.red.bold(dangerousCommand)}\uFF0C${description}`);
504
537
  return confirmAction("\u662F\u5426\u7EE7\u7EED\uFF1F");
505
538
  }
506
539
  function isWorktreeIdle(status) {
@@ -508,17 +541,17 @@ function isWorktreeIdle(status) {
508
541
  }
509
542
  function formatWorktreeStatus(status) {
510
543
  const parts = [];
511
- parts.push(chalk.yellow(`${status.commitCount} \u4E2A\u63D0\u4EA4`));
544
+ parts.push(chalk2.yellow(`${status.commitCount} \u4E2A\u63D0\u4EA4`));
512
545
  if (status.insertions === 0 && status.deletions === 0) {
513
546
  parts.push("\u65E0\u53D8\u66F4");
514
547
  } else {
515
548
  const diffParts = [];
516
- diffParts.push(chalk.green(`+${status.insertions}`));
517
- diffParts.push(chalk.red(`-${status.deletions}`));
549
+ diffParts.push(chalk2.green(`+${status.insertions}`));
550
+ diffParts.push(chalk2.red(`-${status.deletions}`));
518
551
  parts.push(diffParts.join(" "));
519
552
  }
520
553
  if (status.hasDirtyFiles) {
521
- parts.push(chalk.gray("(\u672A\u63D0\u4EA4\u4FEE\u6539)"));
554
+ parts.push(chalk2.gray("(\u672A\u63D0\u4EA4\u4FEE\u6539)"));
522
555
  }
523
556
  return parts.join(" ");
524
557
  }
@@ -826,7 +859,7 @@ async function resolveTargetWorktree(worktrees, messages, branchName) {
826
859
  }
827
860
 
828
861
  // src/commands/list.ts
829
- import chalk2 from "chalk";
862
+ import chalk3 from "chalk";
830
863
  function registerListCommand(program2) {
831
864
  program2.command("list").description("\u5217\u51FA\u5F53\u524D\u9879\u76EE\u6240\u6709 worktree").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action((options) => {
832
865
  handleList(options);
@@ -863,12 +896,12 @@ function printListAsText(projectName, worktrees) {
863
896
  for (const wt of worktrees) {
864
897
  const status = getWorktreeStatus(wt);
865
898
  const isIdle = status ? isWorktreeIdle(status) : false;
866
- const pathDisplay = isIdle ? chalk2.hex("#FF8C00")(wt.path) : wt.path;
899
+ const pathDisplay = isIdle ? chalk3.hex("#FF8C00")(wt.path) : wt.path;
867
900
  printInfo(` ${pathDisplay} [${wt.branch}]`);
868
901
  if (status) {
869
902
  printInfo(` ${formatWorktreeStatus(status)}`);
870
903
  } else {
871
- printInfo(` ${chalk2.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
904
+ printInfo(` ${chalk3.yellow(MESSAGES.WORKTREE_STATUS_UNAVAILABLE)}`);
872
905
  }
873
906
  printInfo("");
874
907
  }
@@ -1467,7 +1500,7 @@ async function handleMerge(options) {
1467
1500
  }
1468
1501
 
1469
1502
  // src/commands/config.ts
1470
- import chalk3 from "chalk";
1503
+ import chalk4 from "chalk";
1471
1504
  function registerConfigCommand(program2) {
1472
1505
  const configCmd = program2.command("config").description("\u67E5\u770B\u548C\u7BA1\u7406\u5168\u5C40\u914D\u7F6E").action(() => {
1473
1506
  handleConfig();
@@ -1480,7 +1513,7 @@ function handleConfig() {
1480
1513
  const config = loadConfig();
1481
1514
  logger.info("config \u547D\u4EE4\u6267\u884C\uFF0C\u5C55\u793A\u5168\u5C40\u914D\u7F6E");
1482
1515
  printInfo(`
1483
- ${chalk3.dim("\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84:")} ${CONFIG_PATH}
1516
+ ${chalk4.dim("\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84:")} ${CONFIG_PATH}
1484
1517
  `);
1485
1518
  printSeparator();
1486
1519
  const keys = Object.keys(DEFAULT_CONFIG);
@@ -1490,8 +1523,8 @@ ${chalk3.dim("\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84:")} ${CONFIG_PATH}
1490
1523
  const description = CONFIG_DESCRIPTIONS[key];
1491
1524
  const formattedValue = formatConfigValue(value);
1492
1525
  if (i === 0) printInfo("");
1493
- printInfo(` ${chalk3.bold(key)}: ${formattedValue}`);
1494
- printInfo(` ${chalk3.dim(description)}`);
1526
+ printInfo(` ${chalk4.bold(key)}: ${formattedValue}`);
1527
+ printInfo(` ${chalk4.dim(description)}`);
1495
1528
  printInfo("");
1496
1529
  }
1497
1530
  printSeparator();
@@ -1511,9 +1544,9 @@ async function handleConfigReset() {
1511
1544
  }
1512
1545
  function formatConfigValue(value) {
1513
1546
  if (typeof value === "boolean") {
1514
- return value ? chalk3.green("true") : chalk3.yellow("false");
1547
+ return value ? chalk4.green("true") : chalk4.yellow("false");
1515
1548
  }
1516
- return chalk3.cyan(String(value));
1549
+ return chalk4.cyan(String(value));
1517
1550
  }
1518
1551
 
1519
1552
  // src/commands/sync.ts
@@ -1604,7 +1637,12 @@ var require2 = createRequire(import.meta.url);
1604
1637
  var { version } = require2("../package.json");
1605
1638
  ensureClawtDirs();
1606
1639
  var program = new Command();
1607
- 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
+ });
1608
1646
  registerListCommand(program);
1609
1647
  registerCreateCommand(program);
1610
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawt",
3
- "version": "2.7.4",
3
+ "version": "2.8.0",
4
4
  "description": "本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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';
@@ -0,0 +1,5 @@
1
+ /** 控制台调试输出的日志前缀标识 */
2
+ export const DEBUG_LOG_PREFIX = '[DEBUG]';
3
+
4
+ /** 控制台调试日志的时间戳格式(精简,不含日期) */
5
+ export const DEBUG_TIMESTAMP_FORMAT = 'HH:mm:ss.SSS';
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);
@@ -1,6 +1,7 @@
1
1
  import winston from 'winston';
2
2
  import DailyRotateFile from 'winston-daily-rotate-file';
3
- import { LOGS_DIR } from '../constants/index.js';
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
+ }