@hunyed15/codecgc 0.2.3 → 0.2.6

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/README.md CHANGED
@@ -25,25 +25,26 @@ CLI 仍然保留,用于本地调试、CI 检查和 MCP 不可用时的回退
25
25
 
26
26
  ## 安装
27
27
 
28
- 全局安装 CLI:
28
+ ### 推荐:全局安装(用户级)
29
29
 
30
30
  ```bash
31
31
  npm install -g @hunyed15/codecgc --registry=https://registry.npmjs.org/
32
32
  ```
33
33
 
34
- 全局安装只提供 `cgc*` 命令,不会默认写入用户级 Claude 配置。
34
+ 全局安装后,`cgc-init`、`cgc-status`、`cgc-doctor` 等命令在任何目录都可用。
35
35
 
36
- 然后在每个目标项目根目录执行项目级安装:
36
+ ### 项目初始化
37
+
38
+ 在项目目录下运行:
37
39
 
38
40
  ```bash
39
41
  cd your-project
40
42
  cgc-init
41
- cgc-start
42
- cgc-status
43
- cgc-doctor
44
43
  ```
45
44
 
46
- Claude 中可以使用对应 slash command:
45
+ 这会初始化项目配置并注册 `/cgc` skill Claude Code。
46
+
47
+ 在 Claude 中也可以使用:
47
48
 
48
49
  ```text
49
50
  /cgc-init
@@ -52,7 +53,28 @@ cgc-doctor
52
53
  /cgc-doctor
53
54
  ```
54
55
 
55
- `/cgc-init` 和 `cgc-init` 默认都是项目级安装。它们会把集成文件写入当前项目,不会写入 `~/.claude` 等全局目录。
56
+ 初始化完成后,可以直接使用:
57
+
58
+ ```text
59
+ /cgc "新增一个登录页面"
60
+ ```
61
+
62
+ 或在命令行:
63
+
64
+ ```bash
65
+ cgc "新增一个登录页面"
66
+ ```
67
+
68
+ ### 备选:项目级安装
69
+
70
+ 如果需要版本隔离(CI、Docker 等场景):
71
+
72
+ ```bash
73
+ npm install @hunyed15/codecgc
74
+ npx cgc-init
75
+ ```
76
+
77
+ **注意**:项目级安装需要通过 `npx` 调用命令。推荐使用全局安装以获得更好的用户体验。
56
78
 
57
79
  ## 安装后生成的项目文件
58
80
 
package/bin/cgc-build.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  process.env.CODECGC_BIN_NAME = "cgc-build";
4
- require("./codecgc.js");
4
+ require("./codecgc.js");
package/bin/cgc-doctor.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  process.env.CODECGC_BIN_NAME = "cgc-doctor";
4
- require("./codecgc.js");
4
+ require("./codecgc.js");
package/bin/cgc-entry.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  process.env.CODECGC_BIN_NAME = "cgc-entry";
4
- require("./codecgc.js");
4
+ require("./codecgc.js");
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+
3
+ process.env.CODECGC_BIN_NAME = "cgc-explain";
4
+ require("./codecgc.js");
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  process.env.CODECGC_BIN_NAME = "cgc-external-audit";
4
- require("./codecgc.js");
4
+ require("./codecgc.js");
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  process.env.CODECGC_BIN_NAME = "cgc-external-status";
4
- require("./codecgc.js");
4
+ require("./codecgc.js");
package/bin/cgc-fix.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  process.env.CODECGC_BIN_NAME = "cgc-fix";
4
- require("./codecgc.js");
4
+ require("./codecgc.js");
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  process.env.CODECGC_BIN_NAME = "cgc-history";
4
- require("./codecgc.js");
4
+ require("./codecgc.js");
package/bin/cgc-init.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  process.env.CODECGC_BIN_NAME = "cgc-init";
4
- require("./codecgc.js");
4
+ require("./codecgc.js");
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  process.env.CODECGC_BIN_NAME = "cgc-lifecycle";
4
- require("./codecgc.js");
4
+ require("./codecgc.js");
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  process.env.CODECGC_BIN_NAME = "cgc-package-audit";
4
- require("./codecgc.js");
4
+ require("./codecgc.js");
package/bin/cgc-plan.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  process.env.CODECGC_BIN_NAME = "cgc-plan";
4
- require("./codecgc.js");
4
+ require("./codecgc.js");
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  process.env.CODECGC_BIN_NAME = "cgc-release-readiness";
4
- require("./codecgc.js");
4
+ require("./codecgc.js");
package/bin/cgc-review.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  process.env.CODECGC_BIN_NAME = "cgc-review";
4
- require("./codecgc.js");
4
+ require("./codecgc.js");
package/bin/cgc-route.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  process.env.CODECGC_BIN_NAME = "cgc-route";
4
- require("./codecgc.js");
4
+ require("./codecgc.js");
package/bin/cgc-start.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  process.env.CODECGC_BIN_NAME = "cgc-start";
4
- require("./codecgc.js");
4
+ require("./codecgc.js");
package/bin/cgc-status.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  process.env.CODECGC_BIN_NAME = "cgc-status";
4
- require("./codecgc.js");
4
+ require("./codecgc.js");
package/bin/cgc-test.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  process.env.CODECGC_BIN_NAME = "cgc-test";
4
- require("./codecgc.js");
4
+ require("./codecgc.js");
package/bin/codecgc.js CHANGED
@@ -8,7 +8,7 @@ const path = require("node:path");
8
8
  const repoRoot = path.resolve(__dirname, "..");
9
9
  const invocationCwd = process.cwd();
10
10
  const args = process.argv.slice(2);
11
- const invokedBinary = (process.env.CODECGC_BIN_NAME || path.basename(process.argv[1] || "cgc")).toLowerCase();
11
+ const invokedBinary = (process.env.CODECGC_BIN_NAME || path.parse(process.argv[1] || "cgc").name).toLowerCase();
12
12
  const packageJson = JSON.parse(readFileSync(path.join(repoRoot, "package.json"), "utf8"));
13
13
  const productVersion = packageJson.version || "0.0.0";
14
14
  const DIRECT_COMMANDS = new Set([
@@ -16,6 +16,7 @@ const DIRECT_COMMANDS = new Set([
16
16
  "install",
17
17
  "status",
18
18
  "doctor",
19
+ "explain",
19
20
  "package-audit",
20
21
  "external-audit",
21
22
  "external-status",
@@ -45,6 +46,7 @@ const helpText = `CodeCGC 命令入口
45
46
  cgc-init [--mode local|status|doctor|start] [--workspace <dir>]
46
47
  cgc-status [--format json|summary]
47
48
  cgc-doctor [--format json|summary]
49
+ cgc-explain <error_code> | --list [--format json|summary]
48
50
  cgc-package-audit [--format json|summary]
49
51
  cgc-external-audit [--format json|summary] [--workspace <dir>]
50
52
  cgc-external-status [--format json|summary] [--workspace <dir>]
@@ -88,6 +90,8 @@ const helpText = `CodeCGC 命令入口
88
90
  cgc-status
89
91
  我想知道运行前置和执行器能不能真正启动
90
92
  cgc-doctor
93
+ 我想查看某个错误代码的详细说明和修复建议
94
+ cgc-explain executor-crash
91
95
  我想确认发布包没有漏掉运行时文件
92
96
  cgc-package-audit
93
97
  我想确认第三方能力接入策略和本地 MCP 注册状态
@@ -110,6 +114,7 @@ const helpText = `CodeCGC 命令入口
110
114
  cgc-init 同步项目级集成面
111
115
  cgc-status 检查集成是否就绪,并给出下一步
112
116
  cgc-doctor 检查运行前置、执行器导入与项目集成状态
117
+ cgc-explain 查看错误代码的详细说明和修复建议
113
118
  cgc-package-audit 检查发布包是否覆盖运行时依赖
114
119
  cgc-external-audit 检查外部能力白名单、接入声明与本地 MCP 观测一致性
115
120
  cgc-external-status 查看外部能力状态面板与本地 MCP 观测结果
@@ -151,6 +156,8 @@ const helpText = `CodeCGC 命令入口
151
156
  cgc-status
152
157
  cgc-status --format summary
153
158
  cgc-doctor --format summary
159
+ cgc-explain executor-crash
160
+ cgc-explain --list
154
161
  cgc-package-audit --format summary
155
162
  cgc-external-status --format summary
156
163
  cgc-external-audit --format summary
@@ -163,6 +170,7 @@ const helpText = `CodeCGC 命令入口
163
170
  环境变量:
164
171
  CODECGC_PYTHON_COMMAND 覆盖产品壳与生成的 MCP 配置所使用的 Python 命令
165
172
  CODECGC_WORKSPACE_ROOT 当当前 shell 目录不是目标项目根目录时,显式覆盖目标工作区
173
+ CODECGC_ERROR_LEVEL 错误输出详细级别:summary(默认,小白可读)、detail(技术细节)、debug(完整日志)
166
174
  `;
167
175
 
168
176
  const startHelpText = `CodeCGC Start
@@ -270,6 +278,41 @@ const statusHelpText = `CodeCGC 安装状态
270
278
  同步或修复当前项目集成面。
271
279
  cgc-doctor
272
280
  检查运行前置、执行器导入和项目集成状态。
281
+ cgc-explain
282
+ 查看错误代码的详细说明和修复建议。
283
+ `;
284
+
285
+ const explainHelpText = `CodeCGC Explain
286
+
287
+ 用法:
288
+ cgc-explain <error_code>
289
+ cgc-explain --list [--format <summary|json>]
290
+
291
+ 用途:
292
+ 查看 CodeCGC 错误代码的详细说明、常见原因和修复建议。
293
+
294
+ 默认行为:
295
+ 传入错误代码时,输出该错误的中文说明和建议操作。
296
+ 使用 --list 时,列出所有可用的错误代码。
297
+
298
+ 主要参数:
299
+ <error_code>
300
+ 要查询的错误代码,例如 executor-crash、scope-error、design-gap。
301
+ --list
302
+ 列出所有可用的错误代码。
303
+ --format <summary|json>
304
+ summary 用于人类可读输出,json 用于程序消费。
305
+
306
+ 推荐用法:
307
+ cgc-explain executor-crash
308
+ cgc-explain scope-error
309
+ cgc-explain --list
310
+
311
+ 相关命令:
312
+ cgc-doctor
313
+ 检查运行前置和执行器环境。
314
+ cgc-route
315
+ 查看当前工作流状态和推荐命令。
273
316
  `;
274
317
 
275
318
  const doctorHelpText = `CodeCGC Doctor
@@ -53,9 +53,21 @@ cgc-review ...
53
53
  cgc-route ...
54
54
  ```
55
55
 
56
- ## 安装边界
56
+ ## 安装方式
57
57
 
58
- CodeCGC 默认使用项目级安装。
58
+ **推荐:全局安装 + 项目初始化**
59
+
60
+ ```bash
61
+ npm install -g @hunyed15/codecgc
62
+ cd your-project
63
+ cgc-init
64
+ ```
65
+
66
+ 全局安装后,`cgc-init`、`cgc-status`、`cgc-doctor` 等命令在任何目录都可用。
67
+
68
+ **项目初始化命令**:
69
+
70
+ 在 Claude 中:
59
71
 
60
72
  ```text
61
73
  /cgc-init
@@ -64,7 +76,7 @@ CodeCGC 默认使用项目级安装。
64
76
  /cgc-doctor
65
77
  ```
66
78
 
67
- CLI 回退:
79
+ 在命令行:
68
80
 
69
81
  ```bash
70
82
  cgc-init
@@ -73,10 +85,20 @@ cgc-status
73
85
  cgc-doctor
74
86
  ```
75
87
 
76
- 规则:
88
+ **备选:项目级安装**
89
+
90
+ 适用于 CI、Docker 或需要版本隔离的场景:
91
+
92
+ ```bash
93
+ npm install @hunyed15/codecgc
94
+ npx cgc-init
95
+ ```
96
+
97
+ **规则**:
77
98
 
78
- - `/cgc-init` 和 `cgc-init` 默认写入当前项目。
79
- - 不要默认写入 `~/.claude`。
99
+ - `cgc-init` 和 `/cgc-init` 默认写入当前项目,不写入 `~/.claude`。
100
+ - 全局安装提供最佳用户体验,初始化命令全局可用。
101
+ - 项目级安装需要通过 `npx` 调用命令。
80
102
  - Windows PowerShell 如拦截 `.ps1` shim,使用 `cgc-init.cmd`、`cgc-status.cmd`、`cgc-doctor.cmd`。
81
103
 
82
104
  ## 写入边界
@@ -20,6 +20,8 @@ import shutil
20
20
 
21
21
  mcp = FastMCP("Codex MCP Server-from guda.studio")
22
22
 
23
+ DEFAULT_CODEX_TIMEOUT_SECONDS = 600
24
+
23
25
  # Mirror of model-routing.yaml frontend_paths — keep these hints in sync with
24
26
  # edit-guard.js and geminimcp/server.py BACKEND_PATH_HINTS.
25
27
  FRONTEND_PATH_HINTS = (
@@ -177,7 +179,25 @@ def _validate_backend_target_paths(target_paths: List[Path]) -> tuple[bool, List
177
179
  return True, policy_checks, ""
178
180
 
179
181
 
180
- def run_shell_command(cmd: list[str]) -> Generator[str, None, None]:
182
+ def _terminate_process_tree(process: subprocess.Popen[str]) -> None:
183
+ """Terminate a process and its children best-effort."""
184
+ if process.poll() is not None:
185
+ return
186
+
187
+ if os.name == "nt":
188
+ subprocess.run(
189
+ ["taskkill", "/PID", str(process.pid), "/T", "/F"],
190
+ stdin=subprocess.DEVNULL,
191
+ stdout=subprocess.DEVNULL,
192
+ stderr=subprocess.DEVNULL,
193
+ check=False,
194
+ )
195
+ return
196
+
197
+ process.kill()
198
+
199
+
200
+ def run_shell_command(cmd: list[str], timeout_seconds: int = DEFAULT_CODEX_TIMEOUT_SECONDS) -> Generator[str, None, None]:
181
201
  """Execute a command and stream its output line-by-line.
182
202
 
183
203
  Args:
@@ -204,6 +224,8 @@ def run_shell_command(cmd: list[str]) -> Generator[str, None, None]:
204
224
 
205
225
  output_queue: queue.Queue[str | None] = queue.Queue()
206
226
  GRACEFUL_SHUTDOWN_DELAY = 0.3
227
+ started_at = time.monotonic()
228
+ timed_out = False
207
229
 
208
230
  def is_turn_completed(line: str) -> bool:
209
231
  """Check if the line indicates turn completion via JSON parsing."""
@@ -237,13 +259,17 @@ def run_shell_command(cmd: list[str]) -> Generator[str, None, None]:
237
259
  break
238
260
  yield line
239
261
  except queue.Empty:
262
+ if timeout_seconds > 0 and time.monotonic() - started_at > timeout_seconds:
263
+ timed_out = True
264
+ _terminate_process_tree(process)
265
+ break
240
266
  if process.poll() is not None and not thread.is_alive():
241
267
  break
242
268
 
243
269
  try:
244
270
  process.wait(timeout=5)
245
271
  except subprocess.TimeoutExpired:
246
- process.kill()
272
+ _terminate_process_tree(process)
247
273
  process.wait()
248
274
  thread.join(timeout=5)
249
275
 
@@ -255,6 +281,13 @@ def run_shell_command(cmd: list[str]) -> Generator[str, None, None]:
255
281
  except queue.Empty:
256
282
  break
257
283
 
284
+ if timed_out:
285
+ raise TimeoutError(
286
+ f"Codex CLI timed out after {timeout_seconds} seconds. "
287
+ "This usually means the CLI was waiting for interactive approval, "
288
+ "network/authentication, or a long-running tool call."
289
+ )
290
+
258
291
 
259
292
  def _execute_codex_session(
260
293
  *,
@@ -268,6 +301,7 @@ def _execute_codex_session(
268
301
  model: str,
269
302
  yolo: bool,
270
303
  profile: str,
304
+ timeout_seconds: int = DEFAULT_CODEX_TIMEOUT_SECONDS,
271
305
  ) -> Dict[str, Any]:
272
306
  """Execute Codex CLI and return the parsed MCP response payload."""
273
307
  if not cd.exists():
@@ -310,7 +344,7 @@ def _execute_codex_session(
310
344
  err_message = ""
311
345
  thread_id: Optional[str] = None
312
346
 
313
- for line in run_shell_command(cmd):
347
+ for line in run_shell_command(cmd, timeout_seconds):
314
348
  try:
315
349
  line_dict = json.loads(line.strip())
316
350
  all_messages.append(line_dict)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hunyed15/codecgc",
3
- "version": "0.2.3",
3
+ "version": "0.2.6",
4
4
  "description": "Claude-hosted multi-model workflow product shell for CodeCGC.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -10,6 +10,7 @@
10
10
  "cgc-init": "bin/cgc-init.js",
11
11
  "cgc-status": "bin/cgc-status.js",
12
12
  "cgc-doctor": "bin/cgc-doctor.js",
13
+ "cgc-explain": "bin/cgc-explain.js",
13
14
  "cgc-package-audit": "bin/cgc-package-audit.js",
14
15
  "cgc-external-audit": "bin/cgc-external-audit.js",
15
16
  "cgc-external-status": "bin/cgc-external-status.js",
@@ -47,7 +48,6 @@
47
48
  "scripts/codecgc_runtime/*.py",
48
49
  "scripts/postinstall_codecgc.js",
49
50
  "scripts/README-codecgc-cli.md",
50
- ".claude/hooks/edit-guard.js",
51
51
  "codecgc/cgc/",
52
52
  "codecgc/cgc-arch/",
53
53
  "codecgc/cgc-build/",
@@ -0,0 +1,172 @@
1
+ """Error code classification and explanation for CodeCGC.
2
+
3
+ Provides human-readable explanations and actionable suggestions for common error scenarios.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from typing import Any
8
+
9
+
10
+ # Error code to explanation mapping
11
+ ERROR_CATALOG: dict[str, dict[str, str]] = {
12
+ "executor-crash": {
13
+ "title": "执行器内部异常",
14
+ "description": "执行器脚本在运行过程中异常退出,未能完成任务。",
15
+ "common_causes": [
16
+ "执行器环境配置不完整(缺少依赖、路径错误)",
17
+ "执行器超时或资源不足",
18
+ "执行器内部代码错误",
19
+ ],
20
+ "suggestions": [
21
+ "运行 cgc-doctor 检查执行器环境配置",
22
+ "查看审计文件中的详细错误日志",
23
+ "如果持续失败,尝试重新安装:cgc-init",
24
+ ],
25
+ },
26
+ "executor-failure": {
27
+ "title": "执行器任务失败",
28
+ "description": "执行器正常运行,但任务执行失败(如代码生成失败、测试未通过)。",
29
+ "common_causes": [
30
+ "目标路径不存在或不可访问",
31
+ "任务契约与实际代码不匹配",
32
+ "执行器返回的结果格式不正确",
33
+ ],
34
+ "suggestions": [
35
+ "检查审计文件中的执行器输出",
36
+ "确认目标路径在工作区中存在",
37
+ "如果是路径问题,回到 cgc-plan 修正步骤契约",
38
+ ],
39
+ },
40
+ "scope-error": {
41
+ "title": "任务范围错误",
42
+ "description": "当前步骤包含多个执行器归属的路径,需要拆分。",
43
+ "common_causes": [
44
+ "一个步骤同时包含前端和后端路径",
45
+ "一个步骤包含 shared 或 unknown 路径",
46
+ ],
47
+ "suggestions": [
48
+ "运行 cgc-plan 查看拆分建议",
49
+ "按执行器归属(frontend/backend)拆分成独立步骤",
50
+ "shared 路径需要先明确归属或单独处理",
51
+ ],
52
+ },
53
+ "design-gap": {
54
+ "title": "设计缺口",
55
+ "description": "当前步骤引用的路径或配置在路由策略中未覆盖,或目标文件不存在。",
56
+ "common_causes": [
57
+ "目标路径在 model-routing.yaml 中未定义",
58
+ "目标文件在工作区中不存在",
59
+ "路由规则与实际项目结构不匹配",
60
+ ],
61
+ "suggestions": [
62
+ "检查 model-routing.yaml 是否覆盖目标路径",
63
+ "确认目标文件在工作区中存在",
64
+ "回到 cgc-plan 修正目标路径或步骤契约",
65
+ ],
66
+ },
67
+ "environment-or-tooling": {
68
+ "title": "环境或工具问题",
69
+ "description": "执行器环境、依赖或外部工具不可用。",
70
+ "common_causes": [
71
+ "执行器 CLI 未安装或不在 PATH 中",
72
+ "执行器超时(网络、认证、长时间运行)",
73
+ "缺少必需的依赖或配置文件",
74
+ ],
75
+ "suggestions": [
76
+ "运行 cgc-doctor 检查执行器可用性",
77
+ "检查网络连接和认证状态",
78
+ "确认执行器 CLI 已正确安装",
79
+ ],
80
+ },
81
+ "workflow-state": {
82
+ "title": "工作流状态不满足",
83
+ "description": "当前工作流状态不允许执行请求的操作。",
84
+ "common_causes": [
85
+ "尝试执行 build/fix/test,但工作流还在 needs-planning 状态",
86
+ "尝试 review,但没有可审核的执行结果",
87
+ "工作流已关闭,但尝试继续执行",
88
+ ],
89
+ "suggestions": [
90
+ "运行 cgc-route 查看当前工作流状态和推荐命令",
91
+ "按推荐命令顺序执行(plan → build/fix → review)",
92
+ "如果工作流已关闭,使用 /cgc 开始新的工作流",
93
+ ],
94
+ },
95
+ "returned-to-planning": {
96
+ "title": "返回规划阶段",
97
+ "description": "执行器发现问题,需要回到规划阶段修正。",
98
+ "common_causes": [
99
+ "任务范围需要拆分(scope-error)",
100
+ "目标路径或契约有设计缺口(design-gap)",
101
+ ],
102
+ "suggestions": [
103
+ "运行 cgc-plan 查看问题详情和修正建议",
104
+ "根据建议修正步骤契约或拆分步骤",
105
+ "修正后重新执行 build/fix",
106
+ ],
107
+ },
108
+ }
109
+
110
+
111
+ def explain_error(error_code: str) -> dict[str, Any]:
112
+ """Get explanation for an error code.
113
+
114
+ Args:
115
+ error_code: Error code from failure_type field
116
+
117
+ Returns:
118
+ Dict with title, description, common_causes, and suggestions
119
+ """
120
+ if error_code not in ERROR_CATALOG:
121
+ return {
122
+ "error_code": error_code,
123
+ "title": "未知错误类型",
124
+ "description": f"错误代码 '{error_code}' 未在错误分类表中定义。",
125
+ "common_causes": [],
126
+ "suggestions": [
127
+ "查看完整错误信息中的 error 和 next 字段",
128
+ "运行 cgc-doctor 检查环境配置",
129
+ "如果问题持续,请报告此错误代码",
130
+ ],
131
+ }
132
+
133
+ explanation = ERROR_CATALOG[error_code].copy()
134
+ explanation["error_code"] = error_code
135
+ return explanation
136
+
137
+
138
+ def list_error_codes() -> list[str]:
139
+ """List all available error codes."""
140
+ return sorted(ERROR_CATALOG.keys())
141
+
142
+
143
+ def format_explanation(explanation: dict[str, Any]) -> str:
144
+ """Format explanation as human-readable text.
145
+
146
+ Args:
147
+ explanation: Result from explain_error
148
+
149
+ Returns:
150
+ Formatted text block
151
+ """
152
+ lines = [
153
+ f"错误代码: {explanation['error_code']}",
154
+ f"标题: {explanation['title']}",
155
+ "",
156
+ "说明:",
157
+ explanation['description'],
158
+ "",
159
+ ]
160
+
161
+ if explanation.get("common_causes"):
162
+ lines.append("常见原因:")
163
+ for cause in explanation["common_causes"]:
164
+ lines.append(f" - {cause}")
165
+ lines.append("")
166
+
167
+ if explanation.get("suggestions"):
168
+ lines.append("建议操作:")
169
+ for suggestion in explanation["suggestions"]:
170
+ lines.append(f" - {suggestion}")
171
+
172
+ return "\n".join(lines)
@@ -0,0 +1,124 @@
1
+ """Error formatting and leveling for CodeCGC output.
2
+
3
+ Provides three-level error display:
4
+ - summary: User-friendly Chinese message for non-technical users
5
+ - detail: Technical context for developers
6
+ - debug: Full logs and tracebacks for troubleshooting
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+
13
+ def format_error_output(
14
+ result: dict[str, Any],
15
+ *,
16
+ level: str = "summary",
17
+ ) -> dict[str, Any]:
18
+ """Format error output with appropriate detail level.
19
+
20
+ Args:
21
+ result: Raw result dict from workflow execution
22
+ level: Display level - "summary", "detail", or "debug"
23
+
24
+ Returns:
25
+ Formatted result with error_display field
26
+ """
27
+ if result.get("success"):
28
+ return result
29
+
30
+ error_display = _build_error_display(result, level)
31
+ formatted = result.copy()
32
+ formatted["error_display"] = error_display
33
+ formatted["error_level"] = level
34
+
35
+ return formatted
36
+
37
+
38
+ def _build_error_display(result: dict[str, Any], level: str) -> dict[str, Any]:
39
+ """Build error display structure based on level."""
40
+ display: dict[str, Any] = {
41
+ "level": level,
42
+ }
43
+
44
+ # Summary level: user-friendly Chinese only
45
+ if level == "summary":
46
+ display["message"] = _extract_user_message(result)
47
+ display["suggestion"] = _extract_user_suggestion(result)
48
+ return display
49
+
50
+ # Detail level: add technical context
51
+ if level == "detail":
52
+ display["message"] = _extract_user_message(result)
53
+ display["suggestion"] = _extract_user_suggestion(result)
54
+ display["technical_error"] = result.get("error", "")
55
+ display["failure_type"] = result.get("failure_type", "")
56
+ display["state"] = result.get("state", "")
57
+ display["audit_path"] = result.get("audit_path", "")
58
+ return display
59
+
60
+ # Debug level: everything
61
+ if level == "debug":
62
+ display["message"] = _extract_user_message(result)
63
+ display["suggestion"] = _extract_user_suggestion(result)
64
+ display["technical_error"] = result.get("error", "")
65
+ display["failure_type"] = result.get("failure_type", "")
66
+ display["state"] = result.get("state", "")
67
+ display["audit_path"] = result.get("audit_path", "")
68
+ display["full_result"] = result
69
+ return display
70
+
71
+ # Fallback
72
+ display["message"] = _extract_user_message(result)
73
+ return display
74
+
75
+
76
+ def _extract_user_message(result: dict[str, Any]) -> str:
77
+ """Extract user-friendly error message."""
78
+ # Check for human_error first (from Phase 1)
79
+ execution = result.get("execution", {})
80
+ if isinstance(execution, dict):
81
+ exec_result = execution.get("result", {})
82
+ if isinstance(exec_result, dict):
83
+ human_error = exec_result.get("human_error", "").strip()
84
+ if human_error:
85
+ return human_error
86
+
87
+ # Check for next field (from flow control)
88
+ next_msg = result.get("next", "").strip()
89
+ if next_msg:
90
+ return next_msg
91
+
92
+ # Check for error field
93
+ error_msg = result.get("error", "").strip()
94
+ if error_msg:
95
+ # If it looks technical, provide generic message
96
+ if "failed with exit code" in error_msg.lower() or "traceback" in error_msg.lower():
97
+ return "执行步骤失败。请查看详细信息或运行 cgc-doctor 检查环境。"
98
+ return error_msg
99
+
100
+ return "执行失败,未提供详细信息。"
101
+
102
+
103
+ def _extract_user_suggestion(result: dict[str, Any]) -> str:
104
+ """Extract user-friendly suggestion."""
105
+ # Check for suggestion field (from Phase 1)
106
+ execution = result.get("execution", {})
107
+ if isinstance(execution, dict):
108
+ exec_result = execution.get("result", {})
109
+ if isinstance(exec_result, dict):
110
+ suggestion = exec_result.get("suggestion", "").strip()
111
+ if suggestion:
112
+ return suggestion
113
+
114
+ # Check for recommended_command
115
+ recommended = result.get("recommended_command", "").strip()
116
+ if recommended:
117
+ return f"建议运行: {recommended}"
118
+
119
+ # Check for next field that contains suggestions
120
+ next_msg = result.get("next", "").strip()
121
+ if next_msg and ("建议" in next_msg or "请" in next_msg or "运行" in next_msg):
122
+ return next_msg
123
+
124
+ return "请稍后重试,或运行 cgc-doctor 检查环境配置。"
@@ -108,6 +108,17 @@ def classify_execution_failure(execution: dict[str, Any]) -> tuple[str, str, str
108
108
  "当前步骤引用的目标路径在工作区中不存在,请先回到 cgc-plan 修正目标路径或步骤契约。",
109
109
  )
110
110
 
111
+ if "failed with exit code" in combined:
112
+ human = str(result.get("human_error", "")).strip()
113
+ suggestion = str(result.get("suggestion", "")).strip()
114
+ next_msg = human + "\n" + suggestion if human else "执行器内部异常,请稍后重试。如果持续失败,运行 cgc-doctor 检查环境。"
115
+ return (
116
+ "blocked",
117
+ "executor-crash",
118
+ "",
119
+ next_msg,
120
+ )
121
+
111
122
  if outcome == "blocked" or "timeout" in combined or "does not exist" in combined:
112
123
  return (
113
124
  "blocked",
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ import os
4
5
  import sys
5
6
  from typing import Any
6
7
 
@@ -20,6 +21,14 @@ def configure_utf8_stdio() -> None:
20
21
 
21
22
  def print_json(payload: dict[str, Any], *, file: Any | None = None) -> None:
22
23
  target = file or sys.stdout
24
+
25
+ # Apply error formatting if this is an error result
26
+ if not payload.get("success") and "error_display" not in payload:
27
+ error_level = os.environ.get("CODECGC_ERROR_LEVEL", "summary")
28
+ if error_level in {"summary", "detail", "debug"}:
29
+ from codecgc_error_formatter import format_error_output
30
+ payload = format_error_output(payload, level=error_level)
31
+
23
32
  text = json.dumps(payload, ensure_ascii=False, indent=2)
24
33
  target.write(text)
25
34
  target.write("\n")
@@ -14,6 +14,21 @@ WORKSPACE = PACKAGE_ROOT
14
14
  PROJECT_WORKSPACE = PROJECT_ROOT
15
15
  SCRIPTS_DIR = WORKSPACE / "scripts"
16
16
 
17
+ _SCRIPT_OPERATION_NAMES = {
18
+ "plan_codecgc_workflow.py": "规划工作流",
19
+ "route_codecgc_workflow.py": "路由检查",
20
+ "run_codecgc_build.py": "构建执行",
21
+ "run_codecgc_fix.py": "修复执行",
22
+ "run_codecgc_test.py": "测试执行",
23
+ "review_codecgc_workflow.py": "审核调度",
24
+ "build_codecgc_task.py": "任务构建",
25
+ "write_codecgc_decision.py": "写入决策",
26
+ "write_codecgc_learning.py": "写入经验",
27
+ "write_codecgc_architecture.py": "写入架构",
28
+ "write_codecgc_requirement.py": "写入需求",
29
+ "init_codecgc_workflow.py": "初始化工作流",
30
+ }
31
+
17
32
 
18
33
  def build_script_command(script_name: str, *args: str) -> list[str]:
19
34
  return [sys.executable, str(SCRIPTS_DIR / script_name), *args]
@@ -63,10 +78,13 @@ def run_json_script(script_name: str, *args: str) -> dict[str, Any]:
63
78
  parsed.setdefault("returncode", completed.returncode)
64
79
  return parsed
65
80
 
81
+ operation = _SCRIPT_OPERATION_NAMES.get(script_name, script_name)
66
82
  return {
67
83
  "success": False,
68
84
  "returncode": completed.returncode,
69
85
  "stdout": stdout,
70
86
  "stderr": stderr,
71
87
  "error": f"{script_name} failed with exit code {completed.returncode}.",
88
+ "human_error": f"{operation}步骤执行失败(内部脚本异常退出)。",
89
+ "suggestion": "请稍后重试。如果多次失败,运行 cgc-doctor 检查环境配置。",
72
90
  }
@@ -0,0 +1,71 @@
1
+ """cgc-explain: Explain CodeCGC error codes and provide actionable suggestions.
2
+
3
+ Usage:
4
+ cgc-explain <error_code>
5
+ cgc-explain --list
6
+ """
7
+ import argparse
8
+ import sys
9
+
10
+ from codecgc_console_io import configure_utf8_stdio
11
+ from codecgc_console_io import print_json
12
+ from codecgc_error_catalog import explain_error
13
+ from codecgc_error_catalog import format_explanation
14
+ from codecgc_error_catalog import list_error_codes
15
+
16
+
17
+ def build_parser() -> argparse.ArgumentParser:
18
+ parser = argparse.ArgumentParser(
19
+ description="Explain CodeCGC error codes and provide actionable suggestions."
20
+ )
21
+ parser.add_argument(
22
+ "error_code",
23
+ nargs="?",
24
+ help="Error code to explain (e.g., executor-crash, scope-error)",
25
+ )
26
+ parser.add_argument(
27
+ "--list",
28
+ action="store_true",
29
+ help="List all available error codes",
30
+ )
31
+ parser.add_argument(
32
+ "--format",
33
+ choices=["json", "summary"],
34
+ default="summary",
35
+ help="Output format",
36
+ )
37
+ return parser
38
+
39
+
40
+ def main() -> int:
41
+ configure_utf8_stdio()
42
+ parser = build_parser()
43
+ args = parser.parse_args()
44
+
45
+ if args.list:
46
+ codes = list_error_codes()
47
+ if args.format == "json":
48
+ print_json({"success": True, "error_codes": codes})
49
+ else:
50
+ print("可用的错误代码:")
51
+ for code in codes:
52
+ print(f" - {code}")
53
+ print("\n使用 cgc-explain <error_code> 查看详细说明")
54
+ return 0
55
+
56
+ if not args.error_code:
57
+ parser.print_help()
58
+ return 1
59
+
60
+ explanation = explain_error(args.error_code)
61
+
62
+ if args.format == "json":
63
+ print_json({"success": True, "explanation": explanation})
64
+ else:
65
+ print(format_explanation(explanation))
66
+
67
+ return 0
68
+
69
+
70
+ if __name__ == "__main__":
71
+ sys.exit(main())
@@ -134,6 +134,27 @@ def build_project_onboarding_text() -> str:
134
134
 
135
135
  This file is generated by `cgc-init` for this project. It is intentionally project-relative and should not contain machine-specific install paths.
136
136
 
137
+ ## Installation
138
+
139
+ **Recommended: Global Install + Project Init**
140
+
141
+ ```bash
142
+ npm install -g @hunyed15/codecgc
143
+ cd your-project
144
+ cgc-init
145
+ ```
146
+
147
+ After global installation, commands like `cgc-init`, `cgc-status`, `cgc-doctor` are available in any directory.
148
+
149
+ **Alternative: Project-Level Install**
150
+
151
+ For CI, Docker, or version isolation scenarios:
152
+
153
+ ```bash
154
+ npm install @hunyed15/codecgc
155
+ npx cgc-init
156
+ ```
157
+
137
158
  ## First Run
138
159
 
139
160
  Inside Claude:
@@ -910,10 +931,29 @@ def build_doctor_fix_command(workspace_root: Path) -> str:
910
931
  return f"cgc-init --workspace {shell_quote(str(workspace_root))}"
911
932
 
912
933
 
934
+ def is_global_npm_install() -> bool:
935
+ """检测当前是否为全局安装的 CodeCGC"""
936
+ try:
937
+ # 检查是否在 npm 全局目录中
938
+ import sys
939
+ executable_path = Path(sys.executable).resolve()
940
+ # 简单启发式:如果在 node_modules/.bin 之外,可能是全局安装
941
+ # 更准确的方法是检查 cgc-init 命令是否在 PATH 中且不需要 npx
942
+ cgc_init_path = shutil.which("cgc-init")
943
+ if cgc_init_path:
944
+ # 如果能直接找到 cgc-init,说明是全局安装
945
+ return True
946
+ except Exception:
947
+ pass
948
+ return False
949
+
950
+
913
951
  def build_install_mode_summary(result: dict[str, Any]) -> str:
914
952
  mode = str(result.get("mode", "")).strip()
915
953
 
916
954
  if mode == "local":
955
+ is_global = is_global_npm_install()
956
+
917
957
  lines = [
918
958
  f"- 工作区: {result.get('workspace', '')}",
919
959
  "- 范围: 项目级 Claude 与 MCP 集成面",
@@ -926,8 +966,17 @@ def build_install_mode_summary(result: dict[str, Any]) -> str:
926
966
  f"- Hook 脚本: {result.get('hook_script', '')}",
927
967
  f"- Slash Commands: {result.get('commands_dir', '')}",
928
968
  f"- 新手入口: {result.get('onboarding_file', '')}",
929
- "- 说明: 可选外部能力如 MemOS 不由 cgc-init 自动写入;如需启用,请在 Claude 中单独配置官方 MCP。",
930
969
  ]
970
+
971
+ if is_global:
972
+ lines.append("- 安装方式: 全局安装(推荐)")
973
+ lines.append("- 说明: 初始化完成。现在可以在 Claude 中使用 /cgc 或在命令行使用 cgc 命令。")
974
+ else:
975
+ lines.append("- 安装方式: 项目级安装")
976
+ lines.append("- 说明: 项目级安装需要通过 npx 调用命令。推荐全局安装:npm install -g @hunyed15/codecgc")
977
+
978
+ lines.append("- 提示: 可选外部能力如 MemOS 不由 cgc-init 自动写入;如需启用,请在 Claude 中单独配置官方 MCP。")
979
+
931
980
  next_actions = [
932
981
  "cgc-start",
933
982
  "cgc-status",
@@ -12,13 +12,31 @@ function isGlobalInstall() {
12
12
  }
13
13
 
14
14
  function main() {
15
- if (!isGlobalInstall()) {
16
- return 0;
15
+ const isGlobal = isGlobalInstall();
16
+
17
+ if (isGlobal) {
18
+ console.log("");
19
+ console.log("✓ CodeCGC 已全局安装");
20
+ console.log("");
21
+ console.log("下一步:");
22
+ console.log(" 1. 进入项目目录:cd your-project");
23
+ console.log(" 2. 初始化项目:cgc-init");
24
+ console.log(" 3. 查看状态:cgc-status");
25
+ console.log("");
26
+ console.log("初始化后,可在 Claude 中使用 /cgc 或在命令行使用 cgc 命令。");
27
+ console.log("");
28
+ } else {
29
+ console.log("");
30
+ console.log("✓ CodeCGC 已安装到项目");
31
+ console.log("");
32
+ console.log("下一步:");
33
+ console.log(" 运行 'npx cgc-init' 来初始化项目");
34
+ console.log("");
35
+ console.log("提示:推荐全局安装以获得更好的体验:");
36
+ console.log(" npm install -g @hunyed15/codecgc");
37
+ console.log("");
17
38
  }
18
39
 
19
- console.warn("[codecgc] Global CLI installed.");
20
- console.warn("[codecgc] CodeCGC no longer writes Claude user-level files during npm install.");
21
- console.warn("[codecgc] Run `cgc-init` from each target project to create project-local .mcp.json, .claude/, and model-routing.yaml.");
22
40
  return 0;
23
41
  }
24
42