@hunyed15/codecgc 0.2.2 → 0.2.5

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.
Files changed (43) hide show
  1. package/README.md +32 -10
  2. package/bin/cgc-build.js +1 -1
  3. package/bin/cgc-doctor.js +1 -1
  4. package/bin/cgc-entry.js +1 -1
  5. package/bin/cgc-explain.js +4 -0
  6. package/bin/cgc-external-audit.js +1 -1
  7. package/bin/cgc-external-status.js +1 -1
  8. package/bin/cgc-fix.js +1 -1
  9. package/bin/cgc-history.js +1 -1
  10. package/bin/cgc-init.js +1 -1
  11. package/bin/cgc-lifecycle.js +1 -1
  12. package/bin/cgc-package-audit.js +1 -1
  13. package/bin/cgc-plan.js +1 -1
  14. package/bin/cgc-release-readiness.js +1 -1
  15. package/bin/cgc-review.js +1 -1
  16. package/bin/cgc-route.js +1 -1
  17. package/bin/cgc-start.js +1 -1
  18. package/bin/cgc-status.js +1 -1
  19. package/bin/cgc-test.js +1 -1
  20. package/bin/codecgc.js +45 -2
  21. package/codecgc/cgc-onboard/SKILL.md +1 -1
  22. package/codecgc/reference/execution-model.md +1 -1
  23. package/codecgc/reference/policy-routing.md +1 -2
  24. package/codecgc/reference/project-structure.md +3 -3
  25. package/codecgc/reference/quickstart.md +1 -1
  26. package/codecgc/reference/shared-conventions.md +1 -1
  27. package/codecgc/templates/project/CLAUDE.md +214 -0
  28. package/codecgc/templates/project/claude/hooks/edit-guard.js +194 -0
  29. package/codecgc/templates/project/claude/settings.local.json +17 -4
  30. package/mcp/codexmcp/src/codexmcp/server.py +38 -4
  31. package/mcp/geminimcp/src/geminimcp/server.py +1 -1
  32. package/package.json +3 -2
  33. package/scripts/audit_codecgc_package_runtime.py +1 -1
  34. package/scripts/codecgc_error_catalog.py +172 -0
  35. package/scripts/codecgc_error_formatter.py +124 -0
  36. package/scripts/codecgc_flow_control.py +11 -0
  37. package/scripts/codecgc_runtime/console.py +9 -0
  38. package/scripts/codecgc_runtime/workflow_runtime.py +18 -0
  39. package/scripts/explain_codecgc_error.py +71 -0
  40. package/scripts/install_codecgc.py +92 -39
  41. package/scripts/postinstall_codecgc.js +23 -5
  42. package/.claude/hooks/route-edit.ps1 +0 -87
  43. package/codecgc/templates/project/claude/hooks/route-edit.ps1 +0 -87
@@ -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())
@@ -21,9 +21,10 @@ from sync_codecgc_mcp_config import write_mcp_config
21
21
  WORKSPACE = PACKAGE_ROOT
22
22
  PROJECT_ROUTING_PATH = WORKSPACE / "model-routing.yaml"
23
23
  PROJECT_TEMPLATES_DIR = WORKSPACE / "codecgc" / "templates" / "project"
24
- PROJECT_HOOK_PATH = PROJECT_TEMPLATES_DIR / "claude" / "hooks" / "route-edit.ps1"
25
- EDIT_GUARDRAIL_MATCHER = "Edit|Write|MultiEdit|Bash|PowerShell"
26
- LEGACY_EDIT_GUARDRAIL_MATCHERS = {"Edit|Write", "Edit|Write|MultiEdit"}
24
+ PROJECT_HOOK_PATH = PROJECT_TEMPLATES_DIR / "claude" / "hooks" / "edit-guard.js"
25
+ PROJECT_CLAUDE_MD_TEMPLATE = PROJECT_TEMPLATES_DIR / "CLAUDE.md"
26
+ CLAUDE_MD_MARKER = "<!-- codecgc:claude-md:v1 -->"
27
+ EDIT_GUARDRAIL_MATCHER = "Edit|Write|MultiEdit"
27
28
  PROJECT_ONBOARDING_RELATIVE_PATH = "codecgc/START_HERE.md"
28
29
  PROJECT_ONBOARDING_MARKER = "<!-- codecgc:onboarding:v1 -->"
29
30
  CLAUDE_SETTINGS_TEMPLATE = PROJECT_TEMPLATES_DIR / "claude" / "settings.local.json"
@@ -38,7 +39,7 @@ DEFAULT_HOOKS = {
38
39
  "hooks": [
39
40
  {
40
41
  "type": "command",
41
- "command": "powershell -ExecutionPolicy Bypass -File .claude/hooks/route-edit.ps1",
42
+ "command": "node .claude/hooks/edit-guard.js",
42
43
  }
43
44
  ],
44
45
  }
@@ -83,7 +84,8 @@ def get_workspace_paths(override_workspace: str = "") -> dict[str, Path]:
83
84
  "settings": claude_dir / "settings.local.json",
84
85
  "legacy_settings": claude_dir / "settings.json",
85
86
  "mcp": root / ".mcp.json",
86
- "hook_script": hooks_dir / "route-edit.ps1",
87
+ "hook_script": hooks_dir / "edit-guard.js",
88
+ "claude_md": claude_dir / "CLAUDE.md",
87
89
  "commands_dir": claude_dir / "commands",
88
90
  "routing_file": root / "model-routing.yaml",
89
91
  "onboarding_file": root / PROJECT_ONBOARDING_RELATIVE_PATH,
@@ -132,6 +134,27 @@ def build_project_onboarding_text() -> str:
132
134
 
133
135
  This file is generated by `cgc-init` for this project. It is intentionally project-relative and should not contain machine-specific install paths.
134
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
+
135
158
  ## First Run
136
159
 
137
160
  Inside Claude:
@@ -165,7 +188,8 @@ cgc "新增一个登录页面,放在 src/components/LoginForm.tsx"
165
188
  .mcp.json
166
189
  model-routing.yaml
167
190
  .claude/settings.local.json
168
- .claude/hooks/route-edit.ps1
191
+ .claude/hooks/edit-guard.js
192
+ .claude/CLAUDE.md
169
193
  .claude/commands/cgc*.md
170
194
  .codex/codecgcrc.json
171
195
  .gemini/policies/codecgc-policy.toml
@@ -250,10 +274,6 @@ def policy_file_is_valid(path: Path) -> bool:
250
274
  return True
251
275
 
252
276
 
253
- def _normalize_command_path_for_markdown(path: Path) -> str:
254
- return str(path).replace("\\", "\\\\")
255
-
256
-
257
277
  def build_mcp_first_command_template(
258
278
  *,
259
279
  filename: str,
@@ -559,7 +579,7 @@ def is_codecgc_route_edit_hook(hook: Any) -> bool:
559
579
  if hook.get("type") != "command":
560
580
  return False
561
581
  command = str(hook.get("command", "")).replace("\\", "/").lower()
562
- return "route-edit.ps1" in command
582
+ return "edit-guard" in command
563
583
 
564
584
 
565
585
  def merge_hook_settings(current: dict[str, Any], command_text: str) -> tuple[dict[str, Any], bool]:
@@ -578,7 +598,7 @@ def merge_hook_settings(current: dict[str, Any], command_text: str) -> tuple[dic
578
598
  for item in list(pre_tool_use):
579
599
  if not isinstance(item, dict):
580
600
  continue
581
- if item.get("matcher") not in LEGACY_EDIT_GUARDRAIL_MATCHERS | {EDIT_GUARDRAIL_MATCHER}:
601
+ if item.get("matcher") != EDIT_GUARDRAIL_MATCHER:
582
602
  continue
583
603
  hook_list = item.get("hooks")
584
604
  if not isinstance(hook_list, list):
@@ -796,32 +816,8 @@ def mcp_server_matches_runtime_shape(server_payload: dict[str, Any], spec: dict[
796
816
  return True
797
817
 
798
818
 
799
- def mcp_config_matches_runtime_shape(payload: dict[str, Any]) -> bool:
800
- servers = payload.get("mcpServers")
801
- if not isinstance(servers, dict):
802
- return False
803
-
804
- for server_name, spec in RUNTIME_MCP_SERVER_SPECS.items():
805
- server_payload = servers.get(server_name)
806
- if not isinstance(server_payload, dict):
807
- return False
808
- if not mcp_server_matches_runtime_shape(server_payload, spec):
809
- return False
810
- return True
811
-
812
-
813
819
  def build_workspace_hook_command(workspace_paths: dict[str, Path]) -> str:
814
- package_root = str(WORKSPACE).replace("'", "''")
815
- workspace_root_path = Path(workspace_paths["root"])
816
- hook_script_path = workspace_paths.get("hook_script", workspace_root_path / ".claude" / "hooks" / "route-edit.ps1")
817
- workspace_root = str(workspace_root_path).replace("'", "''")
818
- hook_script = str(hook_script_path).replace("\\", "/").replace("'", "''")
819
- return (
820
- "powershell -ExecutionPolicy Bypass -Command "
821
- f"\"$env:CODECGC_PACKAGE_ROOT='{package_root}'; "
822
- f"$env:CODECGC_WORKSPACE_ROOT='{workspace_root}'; "
823
- f"& '{hook_script}'\""
824
- )
820
+ return "node .claude/hooks/edit-guard.js"
825
821
 
826
822
 
827
823
  def build_mode_summary_payload(
@@ -841,6 +837,32 @@ def build_mode_summary_payload(
841
837
  return summary
842
838
 
843
839
 
840
+ def install_project_claude_md(target_path: Path) -> str:
841
+ """Install or append CodeCGC rules to .claude/CLAUDE.md.
842
+
843
+ - Does not exist: create with template content.
844
+ - Exists without marker: append template content.
845
+ - Exists with marker: skip (don't overwrite user modifications).
846
+ """
847
+ if not PROJECT_CLAUDE_MD_TEMPLATE.exists():
848
+ return str(target_path)
849
+
850
+ template_content = PROJECT_CLAUDE_MD_TEMPLATE.read_text(encoding="utf-8")
851
+
852
+ if target_path.exists():
853
+ existing = target_path.read_text(encoding="utf-8")
854
+ if CLAUDE_MD_MARKER in existing:
855
+ return str(target_path)
856
+ # Append with separator
857
+ separator = "\n\n---\n\n" if not existing.endswith("\n\n") else ""
858
+ target_path.write_text(existing + separator + template_content, encoding="utf-8")
859
+ else:
860
+ target_path.parent.mkdir(parents=True, exist_ok=True)
861
+ target_path.write_text(template_content, encoding="utf-8")
862
+
863
+ return str(target_path)
864
+
865
+
844
866
  def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
845
867
  workspace_paths = get_workspace_paths(override_workspace)
846
868
  mcp_path = write_mcp_config(workspace_paths["mcp"], workspace_paths["root"])
@@ -861,6 +883,7 @@ def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
861
883
  shutil.copyfile(PROJECT_HOOK_PATH, workspace_paths["hook_script"])
862
884
  written_commands = write_custom_command_files(workspace_paths["commands_dir"], WORKSPACE / "bin")
863
885
  onboarding_file = write_project_onboarding_file(workspace_paths["root"])
886
+ claude_md_path = install_project_claude_md(workspace_paths["claude_md"])
864
887
 
865
888
  summary = build_mode_summary_payload(
866
889
  scope="项目级 Claude 与 MCP 集成面",
@@ -878,6 +901,7 @@ def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
878
901
  "codex_policy": str(codex_policy_path),
879
902
  "gemini_policy": str(gemini_policy_path),
880
903
  "hook_script": str(workspace_paths["hook_script"]),
904
+ "claude_md": str(claude_md_path),
881
905
  "commands_dir": str(workspace_paths["commands_dir"]),
882
906
  "onboarding_file": str(onboarding_file),
883
907
  "workflow_dirs": workflow_dirs,
@@ -887,7 +911,8 @@ def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
887
911
  "Project-local model-routing.yaml was synchronized as the policy source of truth.",
888
912
  "Project-local codecgc workflow directories were initialized.",
889
913
  "Claude pre-edit guardrail hook was synchronized into the target workspace.",
890
- "Claude project permissions were rendered from codecgc/templates/project/claude/settings.local.json.",
914
+ "Claude project permissions were rendered with whitelist (Edit/Write restricted to codecgc, .claude, docs, config).",
915
+ ".claude/CLAUDE.md was installed with CodeCGC workflow rules and three-layer protection guidance.",
891
916
  "Project-local Codex policy contract was synchronized into .codex/codecgcrc.json.",
892
917
  "Project-local Gemini policy was synchronized into .gemini/policies/codecgc-policy.toml.",
893
918
  "Project-local Claude slash commands were synchronized into .claude/commands.",
@@ -906,10 +931,29 @@ def build_doctor_fix_command(workspace_root: Path) -> str:
906
931
  return f"cgc-init --workspace {shell_quote(str(workspace_root))}"
907
932
 
908
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
+
909
951
  def build_install_mode_summary(result: dict[str, Any]) -> str:
910
952
  mode = str(result.get("mode", "")).strip()
911
953
 
912
954
  if mode == "local":
955
+ is_global = is_global_npm_install()
956
+
913
957
  lines = [
914
958
  f"- 工作区: {result.get('workspace', '')}",
915
959
  "- 范围: 项目级 Claude 与 MCP 集成面",
@@ -922,8 +966,17 @@ def build_install_mode_summary(result: dict[str, Any]) -> str:
922
966
  f"- Hook 脚本: {result.get('hook_script', '')}",
923
967
  f"- Slash Commands: {result.get('commands_dir', '')}",
924
968
  f"- 新手入口: {result.get('onboarding_file', '')}",
925
- "- 说明: 可选外部能力如 MemOS 不由 cgc-init 自动写入;如需启用,请在 Claude 中单独配置官方 MCP。",
926
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
+
927
980
  next_actions = [
928
981
  "cgc-start",
929
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
 
@@ -1,87 +0,0 @@
1
- $ErrorActionPreference = "Stop"
2
-
3
- function Write-Approve {
4
- $payload = @{
5
- decision = "approve"
6
- } | ConvertTo-Json -Compress
7
- [Console]::Out.Write($payload)
8
- exit 0
9
- }
10
-
11
- function Write-Deny($reason) {
12
- $payload = @{
13
- decision = "deny"
14
- reason = $reason
15
- } | ConvertTo-Json -Compress
16
- [Console]::Out.Write($payload)
17
- exit 0
18
- }
19
-
20
- $inputJson = [Console]::In.ReadToEnd()
21
- if ([string]::IsNullOrWhiteSpace($inputJson)) {
22
- Write-Approve
23
- }
24
-
25
- $configuredPackageRoot = [Environment]::GetEnvironmentVariable("CODECGC_PACKAGE_ROOT")
26
- if (-not [string]::IsNullOrWhiteSpace($configuredPackageRoot)) {
27
- $packageRoot = Resolve-Path $configuredPackageRoot
28
- } else {
29
- $packageRoot = Resolve-Path (Join-Path $PSScriptRoot "..\..")
30
- }
31
-
32
- $configuredWorkspaceRoot = [Environment]::GetEnvironmentVariable("CODECGC_WORKSPACE_ROOT")
33
- if (-not [string]::IsNullOrWhiteSpace($configuredWorkspaceRoot)) {
34
- $workspaceRoot = Resolve-Path $configuredWorkspaceRoot
35
- } else {
36
- $workspaceRoot = Resolve-Path (Get-Location)
37
- }
38
-
39
- $policyScript = Join-Path $packageRoot "scripts\codecgc_policy.py"
40
- $routingFile = Join-Path $workspaceRoot "model-routing.yaml"
41
- $mcpConfigFile = Join-Path $workspaceRoot ".mcp.json"
42
-
43
- if (-not (Test-Path $policyScript)) {
44
- Write-Deny "CodeCGC: policy checker is missing: $policyScript"
45
- }
46
-
47
- $pythonCommand = [Environment]::GetEnvironmentVariable("CODECGC_PYTHON_COMMAND")
48
- if ([string]::IsNullOrWhiteSpace($pythonCommand) -and (Test-Path $mcpConfigFile)) {
49
- try {
50
- $mcpConfig = Get-Content -Raw $mcpConfigFile | ConvertFrom-Json
51
- $pythonCommand = [string]$mcpConfig.mcpServers.codecgc.command
52
- } catch {
53
- $pythonCommand = ""
54
- }
55
- }
56
- if ([string]::IsNullOrWhiteSpace($pythonCommand)) {
57
- $pythonCommand = "python"
58
- }
59
-
60
- $psi = New-Object System.Diagnostics.ProcessStartInfo
61
- $psi.FileName = $pythonCommand
62
- $escapedPolicyScript = $policyScript.Replace('"', '\"')
63
- $escapedRoutingFile = $routingFile.Replace('"', '\"')
64
- $psi.Arguments = "`"$escapedPolicyScript`" --hook-check --actor claude --operation write --routing-file `"$escapedRoutingFile`""
65
- $psi.RedirectStandardInput = $true
66
- $psi.RedirectStandardOutput = $true
67
- $psi.RedirectStandardError = $true
68
- $psi.UseShellExecute = $false
69
- $psi.CreateNoWindow = $true
70
-
71
- $process = [System.Diagnostics.Process]::Start($psi)
72
- $process.StandardInput.Write($inputJson)
73
- $process.StandardInput.Close()
74
- $stdout = $process.StandardOutput.ReadToEnd()
75
- $stderr = $process.StandardError.ReadToEnd()
76
- $process.WaitForExit()
77
-
78
- if ($process.ExitCode -ne 0) {
79
- $detail = if ([string]::IsNullOrWhiteSpace($stderr)) { $stdout } else { $stderr }
80
- Write-Deny "CodeCGC: policy checker failed. $detail"
81
- }
82
-
83
- if ([string]::IsNullOrWhiteSpace($stdout)) {
84
- Write-Approve
85
- }
86
-
87
- [Console]::Out.Write($stdout)
@@ -1,87 +0,0 @@
1
- $ErrorActionPreference = "Stop"
2
-
3
- function Write-Approve {
4
- $payload = @{
5
- decision = "approve"
6
- } | ConvertTo-Json -Compress
7
- [Console]::Out.Write($payload)
8
- exit 0
9
- }
10
-
11
- function Write-Deny($reason) {
12
- $payload = @{
13
- decision = "deny"
14
- reason = $reason
15
- } | ConvertTo-Json -Compress
16
- [Console]::Out.Write($payload)
17
- exit 0
18
- }
19
-
20
- $inputJson = [Console]::In.ReadToEnd()
21
- if ([string]::IsNullOrWhiteSpace($inputJson)) {
22
- Write-Approve
23
- }
24
-
25
- $configuredPackageRoot = [Environment]::GetEnvironmentVariable("CODECGC_PACKAGE_ROOT")
26
- if (-not [string]::IsNullOrWhiteSpace($configuredPackageRoot)) {
27
- $packageRoot = Resolve-Path $configuredPackageRoot
28
- } else {
29
- $packageRoot = Resolve-Path (Join-Path $PSScriptRoot "..\..")
30
- }
31
-
32
- $configuredWorkspaceRoot = [Environment]::GetEnvironmentVariable("CODECGC_WORKSPACE_ROOT")
33
- if (-not [string]::IsNullOrWhiteSpace($configuredWorkspaceRoot)) {
34
- $workspaceRoot = Resolve-Path $configuredWorkspaceRoot
35
- } else {
36
- $workspaceRoot = Resolve-Path (Get-Location)
37
- }
38
-
39
- $policyScript = Join-Path $packageRoot "scripts\codecgc_policy.py"
40
- $routingFile = Join-Path $workspaceRoot "model-routing.yaml"
41
- $mcpConfigFile = Join-Path $workspaceRoot ".mcp.json"
42
-
43
- if (-not (Test-Path $policyScript)) {
44
- Write-Deny "CodeCGC: policy checker is missing: $policyScript"
45
- }
46
-
47
- $pythonCommand = [Environment]::GetEnvironmentVariable("CODECGC_PYTHON_COMMAND")
48
- if ([string]::IsNullOrWhiteSpace($pythonCommand) -and (Test-Path $mcpConfigFile)) {
49
- try {
50
- $mcpConfig = Get-Content -Raw $mcpConfigFile | ConvertFrom-Json
51
- $pythonCommand = [string]$mcpConfig.mcpServers.codecgc.command
52
- } catch {
53
- $pythonCommand = ""
54
- }
55
- }
56
- if ([string]::IsNullOrWhiteSpace($pythonCommand)) {
57
- $pythonCommand = "python"
58
- }
59
-
60
- $psi = New-Object System.Diagnostics.ProcessStartInfo
61
- $psi.FileName = $pythonCommand
62
- $escapedPolicyScript = $policyScript.Replace('"', '\"')
63
- $escapedRoutingFile = $routingFile.Replace('"', '\"')
64
- $psi.Arguments = "`"$escapedPolicyScript`" --hook-check --actor claude --operation write --routing-file `"$escapedRoutingFile`""
65
- $psi.RedirectStandardInput = $true
66
- $psi.RedirectStandardOutput = $true
67
- $psi.RedirectStandardError = $true
68
- $psi.UseShellExecute = $false
69
- $psi.CreateNoWindow = $true
70
-
71
- $process = [System.Diagnostics.Process]::Start($psi)
72
- $process.StandardInput.Write($inputJson)
73
- $process.StandardInput.Close()
74
- $stdout = $process.StandardOutput.ReadToEnd()
75
- $stderr = $process.StandardError.ReadToEnd()
76
- $process.WaitForExit()
77
-
78
- if ($process.ExitCode -ne 0) {
79
- $detail = if ([string]::IsNullOrWhiteSpace($stderr)) { $stdout } else { $stderr }
80
- Write-Deny "CodeCGC: policy checker failed. $detail"
81
- }
82
-
83
- if ([string]::IsNullOrWhiteSpace($stdout)) {
84
- Write-Approve
85
- }
86
-
87
- [Console]::Out.Write($stdout)