clawt 3.6.0 → 3.6.1

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.
@@ -1,23 +1,15 @@
1
1
  ---
2
2
  name: docs-sync-updater
3
- description: "Use this agent when the user explicitly requests to synchronize documentation files (docs/*.md, README.md) based on recent code changes in the working area or staging area. The docs/ directory contains modular documentation files: spec.md (核心架构规范) and per-command docs (e.g., create.md, merge.md, validate.md, etc.). This agent must NEVER be called proactively or automatically — it must only be invoked when the user explicitly asks for documentation synchronization.\\n\\nExamples:\\n\\n- Example 1:\\n user: \"请同步更新文档\"\\n assistant: \"好的,我来调用文档同步 agent 来根据当前代码变更更新相关文档。\"\\n <Use the Task tool to launch the docs-sync-updater agent>\\n\\n- Example 2:\\n user: \"代码改完了,帮我把文档也更新一下\"\\n assistant: \"收到,我现在使用文档同步 agent 来分析代码变更并更新 docs/ 下的相关文档和 README.md。\"\\n <Use the Task tool to launch the docs-sync-updater agent>\\n\\n- Example 3:\\n user: \"update docs based on my changes\"\\n assistant: \"好的,我来启动文档同步 agent,根据工作区和暂存区的变更同步更新文档。\"\\n <Use the Task tool to launch the docs-sync-updater agent>\\n\\n- Counter-example (DO NOT do this):\\n user: \"我刚加了一个新命令\"\\n assistant: (DO NOT proactively launch this agent. Wait for the user to explicitly request documentation updates.)"
3
+ description: "Use this agent when the user explicitly requests to synchronize documentation files (README.md) based on recent code changes in the working area or staging area. This agent must NEVER be called proactively or automatically — it must only be invoked when the user explicitly asks for documentation synchronization.\n\nExamples:\n\n- Example 1:\n user: \"请同步更新文档\"\n assistant: \"好的,我来调用文档同步 agent 来根据当前代码变更更新相关文档。\"\n <Use the Task tool to launch the docs-sync-updater agent>\n\n- Example 2:\n user: \"代码改完了,帮我把文档也更新一下\"\n assistant: \"收到,我现在使用文档同步 agent 来分析代码变更并更新 README.md。\"\n <Use the Task tool to launch the docs-sync-updater agent>\n\n- Example 3:\n user: \"update docs based on my changes\"\n assistant: \"好的,我来启动文档同步 agent,根据工作区和暂存区的变更同步更新文档。\"\n <Use the Task tool to launch the docs-sync-updater agent>\n\n- Counter-example (DO NOT do this):\n user: \"我刚加了一个新命令\"\n assistant: (DO NOT proactively launch this agent. Wait for the user to explicitly request documentation updates.)"
4
4
  model: opus
5
5
  memory: project
6
6
  ---
7
7
 
8
- 你是一位资深的技术文档工程师,精通代码变更分析与文档同步维护。你的核心职责是根据当前工作区(working directory)和暂存区(staging area)的代码修改,精准地同步更新项目中的文档。
8
+ 你是一位资深的技术文档工程师,精通代码变更分析与文档同步维护。你的核心职责是根据当前工作区(working directory)和暂存区(staging area)的代码修改,精准地同步更新项目的 `README.md`。
9
9
 
10
10
  ## 文档结构
11
11
 
12
- 项目的文档已拆分为模块化结构,位于 `docs/` 目录下:
13
-
14
- - **`docs/spec.md`**:项目核心架构规范(全局设计、验证架构、公共模块等)
15
- - **`docs/<command>.md`**:各命令的独立文档(如 `create.md`、`merge.md`、`validate.md`、`status.md` 等)
16
- - **`docs/config.md`**:配置系统文档
17
- - **`docs/config-file.md`**:配置文件规范
18
- - **`README.md`**:面向用户的快速上手指南
19
-
20
- 当代码变更涉及某个命令时,应更新对应的 `docs/<command>.md` 而非将所有内容塞进 `docs/spec.md`。`docs/spec.md` 仅用于跨命令的全局架构和公共设计。
12
+ 项目的文档为单一的 `README.md` 文件,是面向用户的快速上手指南。
21
13
 
22
14
  ## 重要约束
23
15
 
@@ -38,27 +30,19 @@ memory: project
38
30
  5. 仔细分析变更内容,提取以下信息:
39
31
  - 新增/修改/删除了哪些文件
40
32
  - 新增/修改/删除了哪些功能、命令、函数、类型
41
- - 架构层面的变化(新目录、新模块、依赖变更等)
42
33
  - API 或配置的变化
43
34
  - 构建流程的变化
44
35
 
45
36
  ### 第二步:阅读现有文档
46
37
 
47
- 1. 运行 `ls docs/` 获取 docs 目录下的完整文件列表。
48
- 2. 根据代码变更涉及的模块,读取对应的 `docs/<command>.md` 文档。
49
- 3. 如果变更涉及全局架构或公共模块,读取 `docs/spec.md`。
50
- 4. 如果变更影响用户可见的功能,读取 `README.md`。
51
- 5. 理解每个相关文档的结构、风格和覆盖范围。
38
+ 1. 读取 `README.md`,理解其结构、风格和覆盖范围。
52
39
 
53
40
  ### 第三步:确定需要更新的内容
54
41
 
55
- 根据代码变更的范围,判断需要更新哪些文档:
42
+ 根据代码变更的范围,判断 `README.md` 中哪些部分需要更新:
56
43
 
57
- - **`docs/<command>.md`**(如 `create.md`、`merge.md` 等):当该命令的功能、参数、行为、验证逻辑等发生变化时需要更新。
58
- - **`docs/spec.md`**:当跨命令的全局架构、验证框架、公共模块、项目整体设计发生变化时需要更新。
59
- - **`docs/config.md` / `docs/config-file.md`**:当配置系统或配置文件格式发生变化时需要更新。
60
- - **`README.md`**:当用户可见的功能、安装方式、使用方法、命令参数发生变化时需要更新。
61
- - 如果代码变更新增了一个全新的命令,应创建对应的 `docs/<command>.md` 文件。
44
+ - 用户可见的功能、安装方式、使用方法、命令参数发生变化时需要更新。
45
+ - 如果代码变更只涉及内部重构而不改变用户可见行为,可能不需要更新 `README.md`,此时应告知用户无需更新并说明原因。
62
46
 
63
47
  ### 第四步:执行更新
64
48
 
@@ -66,40 +50,38 @@ memory: project
66
50
  2. **只修改与代码变更相关的部分**——不要重写整个文档。
67
51
  3. **增量更新**——新增内容放在逻辑上合适的位置。
68
52
  4. **保持一致性**——术语、格式、缩进与现有文档保持一致。
69
- 5. **如果某个文档不需要更新,明确说明原因并跳过**。
53
+ 5. **如果不需要更新,明确说明原因并跳过**。
70
54
 
71
55
  ### 第五步:输出更新摘要
72
56
 
73
- 完成所有文档更新后,输出一份简洁的更新摘要:
74
- - 列出每个文档的具体修改内容
75
- - 如果某个文档未做修改,说明原因
76
-
77
- ## 文档更新原则
78
-
79
- 1. **准确性优先**:文档内容必须准确反映代码的实际状态,不要编造或猜测。
80
- 2. **最小变更原则**:只更新与代码变更直接相关的部分,避免不必要的改动。
81
- 3. **向后兼容**:保留现有文档中仍然有效的内容,不要随意删除。
82
- 4. **中文优先**:新增的文档内容使用中文,除非原文档使用英文且保持一致性更好。
83
- 5. **代码示例同步**:如果文档中有代码示例受到变更影响,必须同步更新。
57
+ 完成文档更新后,输出一份简洁的更新摘要:
58
+ - 列出 `README.md` 的具体修改内容
59
+ - 如果未做修改,说明原因
84
60
 
85
61
  ## README.md 编写规则
86
62
 
87
63
  README.md 的定位是**面向新用户的快速上手指南**,必须严格遵守以下规则:
88
64
 
89
65
  1. **只写"怎么用",不写"怎么实现"**:不涉及内部原理、实现细节、技术架构等内容。用户只需要知道命令怎么敲、参数怎么传。
90
- 2. **保持简洁**:每个命令只展示最常用的用法和必要参数,避免罗列所有边界情况和细节行为(如分支匹配策略的内部逻辑、增量模式的 git 底层实现、squash 流程等)。
66
+ 2. **保持简洁**:每个命令只展示最常用的用法和必要参数,避免罗列所有边界情况和细节行为。
91
67
  3. **结构固定**:README.md 应保持以下结构顺序:安装 → 快速开始 → 命令一览 → 配置 → 全局选项 → 日志。不要添加"开发"、"测试"、"技术栈"等面向开发者的章节。
92
- 4. **不包含开发相关内容**:测试命令、构建流程、技术选型、目录结构等开发者信息放在 `docs/spec.md` 或对应的 `docs/<command>.md` 中,不放在 README.md。
68
+ 4. **不包含开发相关内容**:测试命令、构建流程、技术选型、目录结构等开发者信息不放在 README.md。
93
69
  5. **命令说明精简**:每个命令给出简短描述 + 核心用法示例即可,参数表只在确实需要时才添加,且只列必要参数。
94
- 6. **详细的技术规格、设计原理、边界情况处理等内容应更新到 `docs/` 下对应的文档文件中**(命令相关的更新到 `docs/<command>.md`,全局架构更新到 `docs/spec.md`),而非 README.md。
70
+
71
+ ## 文档更新原则
72
+
73
+ 1. **准确性优先**:文档内容必须准确反映代码的实际状态,不要编造或猜测。
74
+ 2. **最小变更原则**:只更新与代码变更直接相关的部分,避免不必要的改动。
75
+ 3. **向后兼容**:保留现有文档中仍然有效的内容,不要随意删除。
76
+ 4. **中文优先**:新增的文档内容使用中文,除非原文档使用英文且保持一致性更好。
77
+ 5. **代码示例同步**:如果文档中有代码示例受到变更影响,必须同步更新。
95
78
 
96
79
  ## 质量检查
97
80
 
98
81
  在完成更新前,自我检查:
99
- - [ ] 所有变更的功能是否都在相关文档中体现?
82
+ - [ ] 所有变更的功能是否都在 README.md 中体现?
100
83
  - [ ] 文档中的代码示例是否仍然正确?
101
84
  - [ ] 命令列表、参数说明是否与代码一致?
102
- - [ ] 目录结构描述是否与实际一致?
103
85
  - [ ] 没有删除任何注释掉的代码或解释性注释?
104
86
  - [ ] 新增注释是否使用中文?
105
87
  - [ ] 文档格式是否与原有风格保持一致?
@@ -107,15 +89,13 @@ README.md 的定位是**面向新用户的快速上手指南**,必须严格遵
107
89
  ## 边界情况处理
108
90
 
109
91
  - 如果工作区和暂存区都没有变更,检查最近的提交并告知用户当前没有未提交的变更,询问是否基于最近提交更新。
110
- - 如果某个文档文件不存在,告知用户并询问是否需要创建。
111
92
  - 如果变更内容过于复杂或不确定如何反映到文档中,列出你的理解并询问用户确认。
112
- - 如果变更只涉及代码重构而不改变功能,可能不需要更新面向用户的文档(README.md),但可能需要更新规格文档(`docs/spec.md` 或对应的 `docs/<command>.md`)。
113
- - 如果变更涉及新增命令,且 `docs/` 下还没有对应的命令文档,应创建 `docs/<command>.md`。
93
+ - 如果变更只涉及代码重构而不改变功能,可能不需要更新 README.md,告知用户即可。
114
94
 
115
95
  **Update your agent memory** as you discover documentation patterns, document structure conventions, terminology usage, and relationships between code modules and their documentation sections. This builds up institutional knowledge across conversations. Write concise notes about what you found and where.
116
96
 
117
97
  Examples of what to record:
118
- - 各文档的章节结构和更新模式
98
+ - README.md 的章节结构和更新模式
119
99
  - 项目术语和命名惯例
120
100
  - 代码模块与文档章节的对应关系
121
101
  - 常见的文档更新场景和处理方式
package/CLAUDE.md ADDED
@@ -0,0 +1,6 @@
1
+ # Project Memory
2
+
3
+ ## 编码规范
4
+
5
+ - JSON 序列化必须使用项目封装的 `safeStringify`(位于 `src/utils/json.ts`),禁止直接使用原生 `JSON.stringify`。`safeStringify` 已通过 `src/utils/index.js` 统一导出。
6
+ - 构造输出对象时,使用 `{ ...obj }` 解构展开而非逐字段列举,避免源类型新增字段后遗漏。
package/dist/index.js CHANGED
@@ -547,7 +547,11 @@ var HOME_MESSAGES = {
547
547
  /** 已在主工作分支上 */
548
548
  HOME_ALREADY_ON_MAIN: (branch) => `\u5DF2\u5728\u4E3B\u5DE5\u4F5C\u5206\u652F ${branch} \u4E0A\uFF0C\u65E0\u9700\u5207\u6362`,
549
549
  /** 切换成功 */
550
- HOME_SWITCH_SUCCESS: (from, to) => `\u2713 \u5DF2\u4ECE ${from} \u5207\u6362\u5230\u4E3B\u5DE5\u4F5C\u5206\u652F ${to}`
550
+ HOME_SWITCH_SUCCESS: (from, to) => `\u2713 \u5DF2\u4ECE ${from} \u5207\u6362\u5230\u4E3B\u5DE5\u4F5C\u5206\u652F ${to}`,
551
+ /** 当前在子 worktree,提示用户手动 cd 到主 worktree */
552
+ HOME_NOT_IN_MAIN_WORKTREE: (mainPath) => `\u5F53\u524D\u4E0D\u5728\u4E3B worktree\uFF0C\u8BF7\u5148\u5207\u6362\u5230\u4E3B worktree\uFF1A
553
+
554
+ cd ${mainPath}`
551
555
  };
552
556
 
553
557
  // src/constants/messages/tasks.ts
@@ -1084,9 +1088,29 @@ import { execSync as execSync2, execFileSync as execFileSync2 } from "child_proc
1084
1088
  function getGitCommonDir(cwd) {
1085
1089
  return execCommand("git rev-parse --git-common-dir", { cwd });
1086
1090
  }
1091
+ function isMainWorktree(cwd) {
1092
+ try {
1093
+ return getGitCommonDir(cwd) === ".git";
1094
+ } catch {
1095
+ return false;
1096
+ }
1097
+ }
1098
+ function isInsideGitRepo(cwd) {
1099
+ try {
1100
+ execCommand("git rev-parse --is-inside-work-tree", { cwd });
1101
+ return true;
1102
+ } catch {
1103
+ return false;
1104
+ }
1105
+ }
1087
1106
  function getGitTopLevel(cwd) {
1088
1107
  return execCommand("git rev-parse --show-toplevel", { cwd });
1089
1108
  }
1109
+ function getMainWorktreePath(cwd) {
1110
+ const output = execCommand("git worktree list --porcelain", { cwd });
1111
+ const firstLine = output.split("\n")[0] || "";
1112
+ return firstLine.replace("worktree ", "");
1113
+ }
1090
1114
  function getProjectName(cwd) {
1091
1115
  const topLevel = getGitTopLevel(cwd);
1092
1116
  return basename(topLevel);
@@ -3315,6 +3339,56 @@ async function checkForUpdates(currentVersion) {
3315
3339
  }
3316
3340
  }
3317
3341
 
3342
+ // src/utils/json.ts
3343
+ function primitiveToString(value) {
3344
+ if (value === void 0) {
3345
+ return "undefined";
3346
+ }
3347
+ if (value === null) {
3348
+ return "null";
3349
+ }
3350
+ if (typeof value === "symbol") {
3351
+ return value.toString();
3352
+ }
3353
+ if (typeof value === "function") {
3354
+ return `[Function: ${value.name || "anonymous"}]`;
3355
+ }
3356
+ return String(value);
3357
+ }
3358
+ function safeStringify(value, indent = 2) {
3359
+ if (value === null || typeof value !== "object") {
3360
+ return primitiveToString(value);
3361
+ }
3362
+ try {
3363
+ const seen = /* @__PURE__ */ new WeakSet();
3364
+ return JSON.stringify(
3365
+ value,
3366
+ (_key, val) => {
3367
+ if (typeof val === "bigint") {
3368
+ return val.toString();
3369
+ }
3370
+ if (typeof val === "undefined" || typeof val === "function" || typeof val === "symbol") {
3371
+ return primitiveToString(val);
3372
+ }
3373
+ if (typeof val === "object" && val !== null) {
3374
+ if (seen.has(val)) {
3375
+ return "[Circular]";
3376
+ }
3377
+ seen.add(val);
3378
+ }
3379
+ return val;
3380
+ },
3381
+ indent
3382
+ );
3383
+ } catch {
3384
+ try {
3385
+ return JSON.stringify(String(value), null, indent);
3386
+ } catch {
3387
+ return "[Unserializable]";
3388
+ }
3389
+ }
3390
+ }
3391
+
3318
3392
  // src/utils/validate-runner.ts
3319
3393
  function handleErrorClipboard(clipboardContent) {
3320
3394
  const success = copyToClipboard(clipboardContent);
@@ -5735,14 +5809,18 @@ function registerInitCommand(program2) {
5735
5809
  await handleInit(options);
5736
5810
  });
5737
5811
  initCmd.addCommand(
5738
- new Cmd("show").description("\u4EA4\u4E92\u5F0F\u67E5\u770B\u548C\u4FEE\u6539\u9879\u76EE\u914D\u7F6E").action(async () => {
5739
- await handleInitShow();
5812
+ new Cmd("show").description("\u4EA4\u4E92\u5F0F\u67E5\u770B\u548C\u4FEE\u6539\u9879\u76EE\u914D\u7F6E\uFF08\u652F\u6301 --json \u683C\u5F0F\u8F93\u51FA\uFF09").option("--json", "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA").action(async (options) => {
5813
+ await handleInitShow(options);
5740
5814
  })
5741
5815
  );
5742
5816
  }
5743
- async function handleInitShow() {
5817
+ async function handleInitShow(options) {
5744
5818
  await runPreChecks({ requireMainWorktree: true, requireProjectConfig: true });
5745
5819
  const config2 = requireProjectConfig();
5820
+ if (options.json) {
5821
+ printInitShowAsJson(config2);
5822
+ return;
5823
+ }
5746
5824
  logger.info("init show \u547D\u4EE4\u6267\u884C\uFF0C\u8FDB\u5165\u4EA4\u4E92\u5F0F\u9879\u76EE\u914D\u7F6E");
5747
5825
  const { key, newValue } = await interactiveConfigEditor(
5748
5826
  config2,
@@ -5753,6 +5831,9 @@ async function handleInitShow() {
5753
5831
  saveProjectConfig(updatedConfig);
5754
5832
  printSuccess(MESSAGES.INIT_SET_SUCCESS(key, String(newValue)));
5755
5833
  }
5834
+ function printInitShowAsJson(config2) {
5835
+ console.log(safeStringify({ ...config2 }));
5836
+ }
5756
5837
  async function handleInit(options) {
5757
5838
  await runPreChecks({ requireMainWorktree: true });
5758
5839
  const existingConfig = loadProjectConfig();
@@ -5780,7 +5861,16 @@ function registerHomeCommand(program2) {
5780
5861
  });
5781
5862
  }
5782
5863
  async function handleHome() {
5783
- await runPreChecks({ requireMainWorktree: true, requireHead: true, requireProjectConfig: true, requireMainBranchExists: true });
5864
+ if (!isInsideGitRepo()) {
5865
+ printError(MESSAGES.NOT_MAIN_WORKTREE);
5866
+ return;
5867
+ }
5868
+ if (!isMainWorktree()) {
5869
+ const mainPath = getMainWorktreePath();
5870
+ printHint(MESSAGES.HOME_NOT_IN_MAIN_WORKTREE(mainPath));
5871
+ return;
5872
+ }
5873
+ await runPreChecks({ requireHead: true, requireProjectConfig: true, requireMainBranchExists: true });
5784
5874
  const mainBranch = getMainWorkBranch();
5785
5875
  const currentBranch = getCurrentBranch();
5786
5876
  if (currentBranch === mainBranch) {
@@ -524,7 +524,11 @@ var HOME_MESSAGES = {
524
524
  /** 已在主工作分支上 */
525
525
  HOME_ALREADY_ON_MAIN: (branch) => `\u5DF2\u5728\u4E3B\u5DE5\u4F5C\u5206\u652F ${branch} \u4E0A\uFF0C\u65E0\u9700\u5207\u6362`,
526
526
  /** 切换成功 */
527
- HOME_SWITCH_SUCCESS: (from, to) => `\u2713 \u5DF2\u4ECE ${from} \u5207\u6362\u5230\u4E3B\u5DE5\u4F5C\u5206\u652F ${to}`
527
+ HOME_SWITCH_SUCCESS: (from, to) => `\u2713 \u5DF2\u4ECE ${from} \u5207\u6362\u5230\u4E3B\u5DE5\u4F5C\u5206\u652F ${to}`,
528
+ /** 当前在子 worktree,提示用户手动 cd 到主 worktree */
529
+ HOME_NOT_IN_MAIN_WORKTREE: (mainPath) => `\u5F53\u524D\u4E0D\u5728\u4E3B worktree\uFF0C\u8BF7\u5148\u5207\u6362\u5230\u4E3B worktree\uFF1A
530
+
531
+ cd ${mainPath}`
528
532
  };
529
533
 
530
534
  // src/constants/messages/tasks.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawt",
3
- "version": "3.6.0",
3
+ "version": "3.6.1",
4
4
  "description": "本地并行执行多个Claude Code Agent任务,融合 Git Worktree 与 Claude Code CLI 的命令行工具",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -5,9 +5,13 @@ import {
5
5
  ensureOnMainWorkBranch,
6
6
  getCurrentBranch,
7
7
  getMainWorkBranch,
8
+ isMainWorktree,
9
+ isInsideGitRepo,
10
+ getMainWorktreePath,
8
11
  printSuccess,
9
12
  printInfo,
10
- guardMainWorkBranchExists,
13
+ printHint,
14
+ printError,
11
15
  } from '../utils/index.js';
12
16
 
13
17
  /**
@@ -25,9 +29,27 @@ export function registerHomeCommand(program: Command): void {
25
29
 
26
30
  /**
27
31
  * 执行 home 命令:切换回主工作分支
32
+ * 三种场景:
33
+ * 1. 不在 git 仓库中 → 报错
34
+ * 2. 在子 worktree 中 → 输出提示和 cd 命令
35
+ * 3. 在主 worktree 中 → 切换到主工作分支
28
36
  */
29
37
  async function handleHome(): Promise<void> {
30
- await runPreChecks({ requireMainWorktree: true, requireHead: true, requireProjectConfig: true, requireMainBranchExists: true });
38
+ // 场景 1:不在 git 仓库中,直接报错
39
+ if (!isInsideGitRepo()) {
40
+ printError(MESSAGES.NOT_MAIN_WORKTREE);
41
+ return;
42
+ }
43
+
44
+ // 场景 2:在子 worktree 中,输出 cd 提示
45
+ if (!isMainWorktree()) {
46
+ const mainPath = getMainWorktreePath();
47
+ printHint(MESSAGES.HOME_NOT_IN_MAIN_WORKTREE(mainPath));
48
+ return;
49
+ }
50
+
51
+ // 场景 3:在主 worktree 中,切换到主工作分支
52
+ await runPreChecks({ requireHead: true, requireProjectConfig: true, requireMainBranchExists: true });
31
53
 
32
54
  const mainBranch = getMainWorkBranch();
33
55
  const currentBranch = getCurrentBranch();
@@ -2,7 +2,7 @@ import type { Command } from 'commander';
2
2
  import { Command as Cmd } from 'commander';
3
3
  import { logger } from '../logger/index.js';
4
4
  import { MESSAGES, PROJECT_CONFIG_DEFINITIONS } from '../constants/index.js';
5
- import type { InitOptions, ProjectConfig } from '../types/index.js';
5
+ import type { InitOptions, InitShowOptions, ProjectConfig } from '../types/index.js';
6
6
  import {
7
7
  runPreChecks,
8
8
  validateHeadExists,
@@ -12,6 +12,7 @@ import {
12
12
  requireProjectConfig,
13
13
  printSuccess,
14
14
  interactiveConfigEditor,
15
+ safeStringify,
15
16
  } from '../utils/index.js';
16
17
 
17
18
  /**
@@ -27,23 +28,31 @@ export function registerInitCommand(program: Command): void {
27
28
  await handleInit(options);
28
29
  });
29
30
 
30
- // 注册 show 子命令:交互式查看和修改项目配置
31
+ // 注册 show 子命令:交互式查看和修改项目配置(支持 --json 格式输出)
31
32
  initCmd.addCommand(
32
33
  new Cmd('show')
33
- .description('交互式查看和修改项目配置')
34
- .action(async () => {
35
- await handleInitShow();
34
+ .description('交互式查看和修改项目配置(支持 --json 格式输出)')
35
+ .option('--json', '以 JSON 格式输出')
36
+ .action(async (options: InitShowOptions) => {
37
+ await handleInitShow(options);
36
38
  }),
37
39
  );
38
40
  }
39
41
 
40
42
  /**
41
43
  * 处理 init show 子命令:交互式面板展示和修改项目配置
44
+ * @param {InitShowOptions} options - 子命令选项
42
45
  */
43
- async function handleInitShow(): Promise<void> {
46
+ async function handleInitShow(options: InitShowOptions): Promise<void> {
44
47
  await runPreChecks({ requireMainWorktree: true, requireProjectConfig: true });
45
48
  const config = requireProjectConfig();
46
49
 
50
+ // --json 模式:直接输出 JSON 格式配置,跳过交互式流程
51
+ if (options.json) {
52
+ printInitShowAsJson(config);
53
+ return;
54
+ }
55
+
47
56
  logger.info('init show 命令执行,进入交互式项目配置');
48
57
 
49
58
  const { key, newValue } = await interactiveConfigEditor(
@@ -59,6 +68,14 @@ async function handleInitShow(): Promise<void> {
59
68
  printSuccess(MESSAGES.INIT_SET_SUCCESS(key as string, String(newValue)));
60
69
  }
61
70
 
71
+ /**
72
+ * 以 JSON 格式输出项目配置
73
+ * @param {ProjectConfig} config - 项目配置对象
74
+ */
75
+ function printInitShowAsJson(config: ProjectConfig): void {
76
+ console.log(safeStringify({ ...config }));
77
+ }
78
+
62
79
  /**
63
80
  * 执行 init 命令的核心逻辑
64
81
  * 无论是否已初始化,始终执行设置/切换主工作分支
@@ -4,4 +4,6 @@ export const HOME_MESSAGES = {
4
4
  HOME_ALREADY_ON_MAIN: (branch: string) => `已在主工作分支 ${branch} 上,无需切换`,
5
5
  /** 切换成功 */
6
6
  HOME_SWITCH_SUCCESS: (from: string, to: string) => `✓ 已从 ${from} 切换到主工作分支 ${to}`,
7
+ /** 当前在子 worktree,提示用户手动 cd 到主 worktree */
8
+ HOME_NOT_IN_MAIN_WORKTREE: (mainPath: string) => `当前不在主 worktree,请先切换到主 worktree:\n\n cd ${mainPath}`,
7
9
  } as const;
@@ -94,6 +94,12 @@ export interface InitOptions {
94
94
  branch?: string;
95
95
  }
96
96
 
97
+ /** init show 子命令选项 */
98
+ export interface InitShowOptions {
99
+ /** 以 JSON 格式输出 */
100
+ json?: boolean;
101
+ }
102
+
97
103
  /** tasks init 命令选项 */
98
104
  export interface TasksInitOptions {
99
105
  /** 输出文件路径(可选,默认 tasks.md) */
@@ -1,5 +1,5 @@
1
1
  export type { ClawtConfig, ConfigItemDefinition, ConfigDefinitions } from './config.js';
2
- export type { CreateOptions, RunOptions, ValidateOptions, MergeOptions, RemoveOptions, ResumeOptions, SyncOptions, ListOptions, StatusOptions, ProjectsOptions, InitOptions, TasksInitOptions } from './command.js';
2
+ export type { CreateOptions, RunOptions, ValidateOptions, MergeOptions, RemoveOptions, ResumeOptions, SyncOptions, ListOptions, StatusOptions, ProjectsOptions, InitOptions, InitShowOptions, TasksInitOptions } from './command.js';
3
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';
@@ -12,6 +12,34 @@ export function getGitCommonDir(cwd?: string): string {
12
12
  return execCommand('git rev-parse --git-common-dir', { cwd });
13
13
  }
14
14
 
15
+ /**
16
+ * 判断当前是否在主 worktree 中
17
+ * 条件:git rev-parse --git-common-dir === ".git"
18
+ * @param {string} [cwd] - 工作目录
19
+ * @returns {boolean} 是否在主 worktree 中
20
+ */
21
+ export function isMainWorktree(cwd?: string): boolean {
22
+ try {
23
+ return getGitCommonDir(cwd) === '.git';
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * 判断当前是否在 git 仓库中
31
+ * @param {string} [cwd] - 工作目录
32
+ * @returns {boolean} 是否在 git 仓库中
33
+ */
34
+ export function isInsideGitRepo(cwd?: string): boolean {
35
+ try {
36
+ execCommand('git rev-parse --is-inside-work-tree', { cwd });
37
+ return true;
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+
15
43
  /**
16
44
  * 获取 git 仓库根目录的绝对路径
17
45
  * @param {string} cwd - 工作目录
@@ -21,6 +49,18 @@ export function getGitTopLevel(cwd?: string): string {
21
49
  return execCommand('git rev-parse --show-toplevel', { cwd });
22
50
  }
23
51
 
52
+ /**
53
+ * 获取主 worktree 的绝对路径
54
+ * 通过 git worktree list --porcelain 解析第一行(始终为主 worktree)
55
+ * @param {string} [cwd] - 工作目录
56
+ * @returns {string} 主 worktree 的绝对路径
57
+ */
58
+ export function getMainWorktreePath(cwd?: string): string {
59
+ const output = execCommand('git worktree list --porcelain', { cwd });
60
+ const firstLine = output.split('\n')[0] || '';
61
+ return firstLine.replace('worktree ', '');
62
+ }
63
+
24
64
  /**
25
65
  * 获取项目名(仓库根目录名称)
26
66
  * @param {string} cwd - 工作目录
@@ -3,7 +3,10 @@ export type { ParallelCommandResult, CommandResultWithStderr, ParallelCommandRes
3
3
  export { copyToClipboard } from './clipboard.js';
4
4
  export {
5
5
  getGitCommonDir,
6
+ isMainWorktree,
7
+ isInsideGitRepo,
6
8
  getGitTopLevel,
9
+ getMainWorktreePath,
7
10
  getProjectName,
8
11
  checkBranchExists,
9
12
  createWorktree,
@@ -26,7 +26,7 @@ export interface PreCheckOptions {
26
26
  /**
27
27
  * 校验当前目录是否为主 worktree 的根目录
28
28
  * 条件:git rev-parse --git-common-dir === ".git"
29
- * @throws {ClawtError} 不在主 worktree 根目录时抛出
29
+ * @throws {ClawtError} 不在主 worktree 根目录时抛出(包括不在 git 仓库中的情况)
30
30
  */
31
31
  export function validateMainWorktree(): void {
32
32
  try {
package/docs/alias.md DELETED
@@ -1,114 +0,0 @@
1
- ### 5.15 命令别名管理
2
-
3
- **命令:**
4
-
5
- ```bash
6
- # 列出所有命令别名
7
- clawt alias
8
- clawt alias list
9
-
10
- # 设置命令别名
11
- clawt alias set <alias> <command>
12
-
13
- # 移除命令别名
14
- clawt alias remove <alias>
15
- ```
16
-
17
- **子命令:**
18
-
19
- | 子命令 | 说明 |
20
- | ------ | ---- |
21
- | `clawt alias` / `clawt alias list` | 列出所有已配置的命令别名 |
22
- | `clawt alias set <alias> <command>` | 设置命令别名,将 `<alias>` 映射到 `<command>` |
23
- | `clawt alias remove <alias>` | 移除指定的命令别名 |
24
-
25
- **参数:**
26
-
27
- | 参数 | 必填 | 说明 |
28
- | ---- | ---- | ---- |
29
- | `<alias>` | 是(set / remove) | 别名名称 |
30
- | `<command>` | 是(set) | 目标内置命令名 |
31
-
32
- **约束规则:**
33
-
34
- 1. **别名不能覆盖内置命令名**:别名不能与任何已注册的内置命令同名(动态检测,当前包括 `list`、`create`、`remove`、`run`、`resume`、`validate`、`cover`、`merge`、`config`、`sync`、`reset`、`status`、`alias`、`projects`、`completion`、`init`、`home`)。如果用户尝试设置与内置命令同名的别名,输出错误提示并返回
35
- 2. **目标必须是内置命令**:别名的目标(`<command>`)必须是已注册的内置命令名。如果指定了不存在的目标命令,输出错误提示并返回
36
- 3. **参数透传**:通过别名调用时,所有选项和参数会完全透传给目标命令,行为与直接调用目标命令完全一致
37
-
38
- **持久化:**
39
-
40
- 别名配置存储在 `~/.clawt/config.json` 的 `aliases` 字段中(类型 `Record<string, string>`,默认 `{}`)。
41
-
42
- **运行流程:**
43
-
44
- #### `alias list`(默认)
45
-
46
- 1. 读取配置文件中的 `aliases` 字段
47
- 2. 如果没有配置任何别名,输出提示 `(无别名)`
48
- 3. 如果有别名,逐行输出所有别名映射
49
-
50
- **输出格式:**
51
-
52
- ```
53
- 当前别名列表:
54
- ────────────────────────────────────────
55
-
56
- l → list
57
- r → run
58
- v → validate
59
-
60
- ────────────────────────────────────────
61
- ```
62
-
63
- #### `alias set <alias> <command>`
64
-
65
- 1. **校验别名不与内置命令冲突**:检查 `<alias>` 是否为内置命令名,是则输出错误提示并返回
66
- 2. **校验目标命令存在**:检查 `<command>` 是否为已注册的内置命令名,不是则输出错误提示并返回
67
- 3. 将别名写入配置文件的 `aliases` 字段(如果别名已存在,覆盖旧值)
68
- 4. 输出成功提示
69
-
70
- **输出格式:**
71
-
72
- ```
73
- ✓ 已设置别名: l → list
74
- ```
75
-
76
- #### `alias remove <alias>`
77
-
78
- 1. 读取配置文件中的 `aliases` 字段
79
- 2. 检查指定的别名是否存在,不存在则输出错误提示并返回
80
- 3. 从 `aliases` 中删除该别名并写入配置文件
81
- 4. 输出成功提示
82
-
83
- **输出格式:**
84
-
85
- ```
86
- ✓ 已移除别名: l
87
- ```
88
-
89
- **别名使用示例:**
90
-
91
- ```bash
92
- # 设置别名
93
- clawt alias set l list
94
- clawt alias set r run
95
- clawt alias set v validate
96
-
97
- # 使用别名(等同于对应的完整命令)
98
- clawt l # 等同于 clawt list
99
- clawt r task.md # 等同于 clawt run task.md
100
-
101
- # 查看所有别名
102
- clawt alias list
103
-
104
- # 移除别名
105
- clawt alias remove l
106
- ```
107
-
108
- **实现要点:**
109
-
110
- - 消息常量定义在 `src/constants/messages/alias.ts`(`ALIAS_MESSAGES`),包括列表为空提示、设置/移除成功、别名不存在、与内置命令冲突、目标命令不存在等消息
111
- - 别名应用逻辑位于 `src/utils/alias.ts` 的 `applyAliases` 函数:在主入口 `src/index.ts` 中,所有命令注册完成后调用,遍历配置中的 `aliases` 映射,通过 Commander.js 的 `.alias()` 方法为对应命令注册别名。如果目标命令不存在则跳过并输出 warn 级别日志
112
- - 内置命令冲突检测通过 `getRegisteredCommandNames(program)` 动态获取所有已注册命令名,而非硬编码命令列表
113
-
114
- ---