clawt 3.9.1 → 3.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -649,13 +649,6 @@ var PANEL_FOOTER_SHORTCUTS = Object.entries(SHORTCUT_LABELS).map(([key, label])
649
649
  var PANEL_FOOTER_COUNTDOWN = (seconds) => chalk.gray(`(${seconds}s \u540E\u5237\u65B0)`);
650
650
  var PANEL_OVERFLOW_DOWN_HINT = chalk.gray("\u2193 \u66F4\u591A worktree...");
651
651
  var PANEL_OVERFLOW_UP_HINT = chalk.gray("\u2191 \u66F4\u591A worktree...");
652
- var PANEL_SNAPSHOT_SUMMARY = (total, orphaned) => {
653
- const base = `\u5FEB\u7167: ${total} \u4E2A`;
654
- if (orphaned > 0) {
655
- return `${base}\uFF08${chalk.yellow(`${orphaned} \u4E2A\u5B64\u7ACB`)}\uFF09`;
656
- }
657
- return base;
658
- };
659
652
  var PANEL_NO_WORKTREES = "(\u65E0\u6D3B\u8DC3 worktree)";
660
653
  var PANEL_PRESS_ENTER_TO_RETURN = chalk.gray("\n\u6309 Enter \u8FD4\u56DE\u9762\u677F...");
661
654
  var PANEL_NOT_TTY = "\u4EA4\u4E92\u5F0F\u9762\u677F\u9700\u8981 TTY \u7EC8\u7AEF\u73AF\u5883\uFF0C\u8BF7\u76F4\u63A5\u5728\u7EC8\u7AEF\u4E2D\u8FD0\u884C clawt status -i";
@@ -3761,7 +3754,7 @@ function buildPanelFrame(statusResult, selectedIndex, scrollOffset, rows, cols,
3761
3754
  const lines = [];
3762
3755
  lines.push(PANEL_TITLE(statusResult.main.projectName));
3763
3756
  lines.push(renderConfiguredBranchLine(statusResult.main));
3764
- lines.push(renderSnapshotSummary(statusResult.snapshots.total, statusResult.snapshots.orphaned));
3757
+ lines.push(renderMainBranchDiff(statusResult.main));
3765
3758
  const visibleRows = calculateVisibleRows(rows);
3766
3759
  if (statusResult.worktrees.length === 0) {
3767
3760
  lines.push(buildSeparatorWithHint(cols, ""));
@@ -3896,8 +3889,13 @@ function renderConfiguredBranchLine(main2) {
3896
3889
  }
3897
3890
  return PANEL_CONFIGURED_BRANCH(main2.configuredMainBranch);
3898
3891
  }
3899
- function renderSnapshotSummary(total, orphaned) {
3900
- return PANEL_SNAPSHOT_SUMMARY(total, orphaned);
3892
+ function renderMainBranchDiff(main2) {
3893
+ if (main2.insertions === 0 && main2.deletions === 0) {
3894
+ return `\u5DE5\u4F5C\u533A: ${chalk9.green(MESSAGES.STATUS_CHANGE_CLEAN)}`;
3895
+ }
3896
+ const insertText = chalk9.green(`+${main2.insertions}`);
3897
+ const deleteText = chalk9.red(`-${main2.deletions}`);
3898
+ return `\u5DE5\u4F5C\u533A: ${insertText} ${deleteText}`;
3901
3899
  }
3902
3900
  function renderFooter(countdown) {
3903
3901
  return `${PANEL_FOOTER_SHORTCUTS} ${PANEL_FOOTER_COUNTDOWN(countdown)}`;
@@ -5479,12 +5477,15 @@ function collectStatus() {
5479
5477
  const projectConfig = loadProjectConfig();
5480
5478
  const configuredMainBranch = projectConfig?.clawtMainWorkBranch || null;
5481
5479
  const configuredBranchExists = configuredMainBranch ? checkBranchExists(configuredMainBranch) : null;
5480
+ const { insertions, deletions } = countDiffStat(process.cwd());
5482
5481
  const main2 = {
5483
5482
  branch: currentBranch,
5484
5483
  isClean,
5485
5484
  projectName,
5486
5485
  configuredMainBranch,
5487
- configuredBranchExists
5486
+ configuredBranchExists,
5487
+ insertions,
5488
+ deletions
5488
5489
  };
5489
5490
  const worktrees = getProjectWorktrees();
5490
5491
  const worktreeStatuses = worktrees.map((wt) => collectWorktreeDetailedStatus(wt, projectName));
package/docs/status.md CHANGED
@@ -131,7 +131,9 @@ clawt status [--json] [-i | --interactive]
131
131
  "isClean": true,
132
132
  "projectName": "main-project",
133
133
  "configuredMainBranch": "main",
134
- "configuredBranchExists": true
134
+ "configuredBranchExists": true,
135
+ "insertions": 185,
136
+ "deletions": 42
135
137
  },
136
138
  "worktrees": [
137
139
  {
@@ -163,10 +165,12 @@ clawt status [--json] [-i | --interactive]
163
165
  | `projectName` | `string` | 项目名 |
164
166
  | `configuredMainBranch` | `string \| null` | 配置的主工作分支名(项目未初始化时为 null) |
165
167
  | `configuredBranchExists`| `boolean \| null` | 配置的主工作分支是否存在(项目未初始化时为 null)|
168
+ | `insertions` | `number` | 工作区和暂存区的新增行数 |
169
+ | `deletions` | `number` | 工作区和暂存区的删除行数 |
166
170
 
167
171
  **实现要点:**
168
172
 
169
- - 类型定义在 `src/types/status.ts`:`WorktreeDetailedStatus`(`snapshotTime: string | null`、`createdAt: string | null`)、`MainWorktreeStatus`(包含 `configuredMainBranch` 和 `configuredBranchExists`)、`SnapshotInfo`、`SnapshotSummary`(包含 `total` 和 `orphaned`)、`StatusResult`(`snapshots` 为 `SnapshotSummary` 类型)
173
+ - 类型定义在 `src/types/status.ts`:`WorktreeDetailedStatus`(`snapshotTime: string | null`、`createdAt: string | null`)、`MainWorktreeStatus`(包含 `configuredMainBranch`、`configuredBranchExists`、`insertions`、`deletions`)、`SnapshotInfo`、`SnapshotSummary`(包含 `total` 和 `orphaned`)、`StatusResult`(`snapshots` 为 `SnapshotSummary` 类型)
170
174
  - 消息常量在 `MESSAGES.STATUS_*` 系列:
171
175
  - `STATUS_TITLE(projectName)`:标题文本
172
176
  - `STATUS_MAIN_SECTION`:主 worktree 区块标题
@@ -202,7 +206,7 @@ clawt status [--json] [-i | --interactive]
202
206
  ```
203
207
  项目状态总览: my-project
204
208
  主工作分支: main
205
- 快照: 3 个(1 个孤立)
209
+ 工作区: +185 -42
206
210
  ──────── ↑ 更多 worktree... ────────
207
211
  ════ 2026-03-01(2 天前) ════
208
212
 
@@ -239,7 +243,7 @@ clawt status [--json] [-i | --interactive]
239
243
  - 分支已删除(红色):`✗ 主工作分支: <branchName>(已不存在)`
240
244
  - 分支不一致(黄色):`⚠ 主工作分支: <branchName>(不一致)`
241
245
  - 未初始化(灰色):`未初始化(执行 clawt init 设置主工作分支)`
242
- 3. **快照摘要行**:显示快照总数和孤立快照数
246
+ 3. **工作区 diff 信息行**:显示主工作分支的工作区 diff 统计,有变更时格式为 `工作区: +N -M`(新增行数绿色,删除行数红色),无变更时显示 `工作区: 无变更`(绿色)
243
247
  4. **顶部分隔线**:当存在向上溢出时,分隔线中间嵌入 `↑ 更多 worktree...` 提示
244
248
  5. **Worktree 滚动区域**:按日期分组显示 worktree 列表,支持上下滚动
245
249
  6. **底部分隔线**:当存在向下溢出时,分隔线中间嵌入 `↓ 更多 worktree...` 提示
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawt",
3
- "version": "3.9.1",
3
+ "version": "3.9.2",
4
4
  "description": "本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -81,6 +81,9 @@ export function collectStatus(): StatusResult {
81
81
  const configuredMainBranch = projectConfig?.clawtMainWorkBranch || null;
82
82
  const configuredBranchExists = configuredMainBranch ? checkBranchExists(configuredMainBranch) : null;
83
83
 
84
+ // 主 worktree 的 diff 统计
85
+ const { insertions, deletions } = countDiffStat(process.cwd());
86
+
84
87
  // 主 worktree 状态
85
88
  const main: MainWorktreeStatus = {
86
89
  branch: currentBranch,
@@ -88,6 +91,8 @@ export function collectStatus(): StatusResult {
88
91
  projectName,
89
92
  configuredMainBranch,
90
93
  configuredBranchExists,
94
+ insertions,
95
+ deletions,
91
96
  };
92
97
 
93
98
  // 各 worktree 详细状态
@@ -32,6 +32,10 @@ export interface MainWorktreeStatus {
32
32
  configuredMainBranch: string | null;
33
33
  /** 配置的主工作分支是否存在(项目未初始化时为 null) */
34
34
  configuredBranchExists: boolean | null;
35
+ /** 工作区和暂存区的新增行数 */
36
+ insertions: number;
37
+ /** 工作区和暂存区的删除行数 */
38
+ deletions: number;
35
39
  }
36
40
 
37
41
  /** validate 快照信息 */
@@ -87,8 +87,8 @@ export function buildPanelFrame(
87
87
  // 配置分支信息行
88
88
  lines.push(renderConfiguredBranchLine(statusResult.main));
89
89
 
90
- // 快照摘要行
91
- lines.push(renderSnapshotSummary(statusResult.snapshots.total, statusResult.snapshots.orphaned));
90
+ // 主工作分支 diff 信息行
91
+ lines.push(renderMainBranchDiff(statusResult.main));
92
92
 
93
93
  // 计算可用的 worktree 显示区域行数
94
94
  const visibleRows = calculateVisibleRows(rows);
@@ -315,6 +315,21 @@ function renderConfiguredBranchLine(main: MainWorktreeStatus): string {
315
315
  return PANEL_CONFIGURED_BRANCH(main.configuredMainBranch);
316
316
  }
317
317
 
318
+ /**
319
+ * 渲染主工作分支的 diff 信息行
320
+ * 格式:工作区: +N -M(绿色新增,红色删除)或 工作区: 无变更(绿色)
321
+ * @param {MainWorktreeStatus} main - 主 worktree 状态
322
+ * @returns {string} 格式化的 diff 信息
323
+ */
324
+ function renderMainBranchDiff(main: MainWorktreeStatus): string {
325
+ if (main.insertions === 0 && main.deletions === 0) {
326
+ return `工作区: ${chalk.green(MESSAGES.STATUS_CHANGE_CLEAN)}`;
327
+ }
328
+ const insertText = chalk.green(`+${main.insertions}`);
329
+ const deleteText = chalk.red(`-${main.deletions}`);
330
+ return `工作区: ${insertText} ${deleteText}`;
331
+ }
332
+
318
333
  /**
319
334
  * 渲染快照摘要行
320
335
  * @param {number} total - 快照总数
@@ -340,4 +340,41 @@ describe('handleStatus', () => {
340
340
  const unverifiedLine = printedLines.find((line) => line.includes('未验证'));
341
341
  expect(unverifiedLine).toBeUndefined();
342
342
  });
343
+
344
+ it('主 worktree diff 统计包含在 JSON 输出中', async () => {
345
+ // 模拟主 worktree 有变更
346
+ mockedGetDiffStat.mockReturnValue({ insertions: 185, deletions: 42 });
347
+
348
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
349
+
350
+ const program = new Command();
351
+ program.exitOverride();
352
+ registerStatusCommand(program);
353
+ await program.parseAsync(['status', '--json'], { from: 'user' });
354
+
355
+ const jsonCall = consoleSpy.mock.calls.find((call) => {
356
+ try { JSON.parse(call[0]); return true; } catch { return false; }
357
+ });
358
+ const parsed = JSON.parse(jsonCall![0]);
359
+ expect(parsed.main.insertions).toBe(185);
360
+ expect(parsed.main.deletions).toBe(42);
361
+ });
362
+
363
+ it('主 worktree 无变更时 diff 统计为 0', async () => {
364
+ mockedGetDiffStat.mockReturnValue({ insertions: 0, deletions: 0 });
365
+
366
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
367
+
368
+ const program = new Command();
369
+ program.exitOverride();
370
+ registerStatusCommand(program);
371
+ await program.parseAsync(['status', '--json'], { from: 'user' });
372
+
373
+ const jsonCall = consoleSpy.mock.calls.find((call) => {
374
+ try { JSON.parse(call[0]); return true; } catch { return false; }
375
+ });
376
+ const parsed = JSON.parse(jsonCall![0]);
377
+ expect(parsed.main.insertions).toBe(0);
378
+ expect(parsed.main.deletions).toBe(0);
379
+ });
343
380
  });