@hunyed15/codecgc 0.1.1 → 0.1.3
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/INSTALLATION.md +18 -0
- package/README.md +37 -3
- package/codecgcmcp/README.md +17 -0
- package/codecgcmcp/pyproject.toml +19 -0
- package/codecgcmcp/src/codecgcmcp/__init__.py +1 -0
- package/codecgcmcp/src/codecgcmcp/cli.py +12 -0
- package/codecgcmcp/src/codecgcmcp/server.py +525 -0
- package/model-routing.yaml +4 -0
- package/package.json +4 -1
- package/scripts/codecgc_workflow_runtime.py +1 -0
- package/scripts/install_codecgc.py +355 -5
- package/scripts/sync_codecgc_mcp_config.py +18 -1
package/INSTALLATION.md
CHANGED
|
@@ -47,6 +47,24 @@ npm install -g @hunyed15/codecgc --registry=https://registry.npmjs.org/
|
|
|
47
47
|
cgc-install --mode user
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
这会写入:
|
|
51
|
+
|
|
52
|
+
- `~/.claude/mcp.json`
|
|
53
|
+
- `~/.claude/hooks/route-edit.ps1`
|
|
54
|
+
- `~/.claude/commands/cgc.md`
|
|
55
|
+
- `~/.claude/commands/cgc-install.md`
|
|
56
|
+
- `~/.claude/commands/cgc-status.md`
|
|
57
|
+
- `~/.claude/commands/cgc-doctor.md`
|
|
58
|
+
|
|
59
|
+
写入完成后,可以在 Claude 中直接输入:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
/cgc
|
|
63
|
+
/cgc-install
|
|
64
|
+
/cgc-status
|
|
65
|
+
/cgc-doctor
|
|
66
|
+
```
|
|
67
|
+
|
|
50
68
|
如果安装阶段因为 Python 未就绪而跳过了这一步,可以在装好 Python 后手动补执行该命令。
|
|
51
69
|
|
|
52
70
|
安装后,以下命令将全局可用:
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# CodeCGC Release v0.1.
|
|
1
|
+
# CodeCGC Release v0.1.2
|
|
2
2
|
|
|
3
3
|
## 📦 发布内容
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
## 📋 版本信息
|
|
8
8
|
|
|
9
|
-
- **版本号**: 0.1.
|
|
9
|
+
- **版本号**: 0.1.2
|
|
10
10
|
- **发布日期**: 2026-05-04
|
|
11
11
|
- **Python 要求**: >= 3.10
|
|
12
12
|
- **Node.js 要求**: >= 20.0.0
|
|
@@ -46,7 +46,41 @@ pip install pyyaml
|
|
|
46
46
|
cgc-install --mode user
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
全局安装完成后,CodeCGC 会尝试自动写入 Claude 用户级集成到 `~/.claude
|
|
49
|
+
全局安装完成后,CodeCGC 会尝试自动写入 Claude 用户级集成到 `~/.claude`,包括:
|
|
50
|
+
|
|
51
|
+
- `~/.claude/mcp.json`
|
|
52
|
+
- `~/.claude/hooks/route-edit.ps1`
|
|
53
|
+
- `~/.claude/commands/cgc*.md` 自定义 slash commands
|
|
54
|
+
|
|
55
|
+
当前安装链路还会注册 3 个 MCP server:
|
|
56
|
+
|
|
57
|
+
- `codecgc`:CodeCGC 编排器 MCP
|
|
58
|
+
- `codex`:后端执行器 MCP
|
|
59
|
+
- `gemini`:前端执行器 MCP
|
|
60
|
+
|
|
61
|
+
安装完成后,可以在 Claude 中直接使用:
|
|
62
|
+
|
|
63
|
+
- `/cgc`
|
|
64
|
+
- `/cgc-install`
|
|
65
|
+
- `/cgc-status`
|
|
66
|
+
- `/cgc-doctor`
|
|
67
|
+
- `/cgc-plan`
|
|
68
|
+
- `/cgc-build`
|
|
69
|
+
- `/cgc-fix`
|
|
70
|
+
- `/cgc-test`
|
|
71
|
+
- `/cgc-review`
|
|
72
|
+
- `/cgc-route`
|
|
73
|
+
- `/cgc-history`
|
|
74
|
+
- `/cgc-package-audit`
|
|
75
|
+
- `/cgc-external-audit`
|
|
76
|
+
- `/cgc-release-readiness`
|
|
77
|
+
- `/cgc-lifecycle`
|
|
78
|
+
|
|
79
|
+
当前这些 Claude commands 已调整为:
|
|
80
|
+
|
|
81
|
+
- 优先走 `codecgc` MCP orchestrator
|
|
82
|
+
- 只有在 MCP tool 路径不可用时,才回退到本地 CLI
|
|
83
|
+
|
|
50
84
|
如果安装时 Python 尚未就绪,自动集成会跳过,此时可在安装 Python 后手动执行 `cgc-install --mode user`。
|
|
51
85
|
|
|
52
86
|
### 2. 初始化项目
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# CodeCGC MCP Server
|
|
2
|
+
|
|
3
|
+
This package contains the first minimal CodeCGC orchestrator MCP server.
|
|
4
|
+
|
|
5
|
+
Current scope:
|
|
6
|
+
|
|
7
|
+
- install
|
|
8
|
+
- status
|
|
9
|
+
- doctor
|
|
10
|
+
- entry
|
|
11
|
+
- continue
|
|
12
|
+
- explain
|
|
13
|
+
- review
|
|
14
|
+
- history
|
|
15
|
+
- route
|
|
16
|
+
|
|
17
|
+
This server currently reuses the existing CodeCGC runtime scripts rather than replacing them.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.25"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "codecgcmcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "FastMCP orchestrator server for CodeCGC."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"mcp[cli]>=1.21.2",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
codecgcmcp = "codecgcmcp.cli:main"
|
|
17
|
+
|
|
18
|
+
[tool.hatch.build.targets.wheel]
|
|
19
|
+
packages = ["src/codecgcmcp"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CodeCGC MCP orchestrator package."""
|
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
"""FastMCP server implementation for the CodeCGC orchestrator."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Annotated, Any, Literal
|
|
8
|
+
|
|
9
|
+
from mcp.server.fastmcp import FastMCP
|
|
10
|
+
from mcp.types import CallToolResult
|
|
11
|
+
from mcp.types import TextContent
|
|
12
|
+
from pydantic import Field
|
|
13
|
+
|
|
14
|
+
from codecgc_workflow_runtime import run_json_script
|
|
15
|
+
|
|
16
|
+
mcp = FastMCP("CodeCGC MCP Server")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _append_repeated_flag(args: list[str], flag: str, values: list[str]) -> None:
|
|
20
|
+
for value in values:
|
|
21
|
+
text = str(value).strip()
|
|
22
|
+
if text:
|
|
23
|
+
args.extend([flag, text])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _normalize_workspace(workspace: str) -> str:
|
|
27
|
+
return str(Path(workspace).expanduser().resolve()) if str(workspace).strip() else ""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _as_tool_result(payload: dict[str, Any]) -> CallToolResult:
|
|
31
|
+
return CallToolResult(
|
|
32
|
+
content=[
|
|
33
|
+
TextContent(
|
|
34
|
+
type="text",
|
|
35
|
+
text=json.dumps(payload, ensure_ascii=True, indent=2),
|
|
36
|
+
)
|
|
37
|
+
],
|
|
38
|
+
isError=not bool(payload.get("success", True)),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@mcp.tool(
|
|
43
|
+
name="codecgc.install",
|
|
44
|
+
description="Install or sync CodeCGC integration for the current project or Claude user profile.",
|
|
45
|
+
meta={"version": "0.1.0", "author": "CodeCGC"},
|
|
46
|
+
)
|
|
47
|
+
async def codecgc_install(
|
|
48
|
+
mode: Annotated[
|
|
49
|
+
Literal["local", "user-dry-run", "user", "status", "doctor"],
|
|
50
|
+
Field(description="Install mode for CodeCGC integration."),
|
|
51
|
+
] = "local",
|
|
52
|
+
format: Annotated[
|
|
53
|
+
Literal["summary", "json"],
|
|
54
|
+
Field(description="Output format. Use summary for normal product-facing replies."),
|
|
55
|
+
] = "summary",
|
|
56
|
+
workspace: Annotated[
|
|
57
|
+
str,
|
|
58
|
+
Field(description="Optional target workspace root for local/status/doctor modes."),
|
|
59
|
+
] = "",
|
|
60
|
+
user_root: Annotated[
|
|
61
|
+
str,
|
|
62
|
+
Field(description="Optional explicit Claude user root for user/user-dry-run modes."),
|
|
63
|
+
] = "",
|
|
64
|
+
) -> CallToolResult:
|
|
65
|
+
args = ["--mode", mode, "--format", format]
|
|
66
|
+
normalized_workspace = _normalize_workspace(workspace)
|
|
67
|
+
if normalized_workspace:
|
|
68
|
+
args.extend(["--workspace", normalized_workspace])
|
|
69
|
+
if str(user_root).strip():
|
|
70
|
+
args.extend(["--user-root", str(user_root).strip()])
|
|
71
|
+
return _as_tool_result(run_json_script("install_codecgc.py", *args))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@mcp.tool(
|
|
75
|
+
name="codecgc.status",
|
|
76
|
+
description="Check CodeCGC integration readiness for the current or specified workspace.",
|
|
77
|
+
meta={"version": "0.1.0", "author": "CodeCGC"},
|
|
78
|
+
)
|
|
79
|
+
async def codecgc_status(
|
|
80
|
+
workspace: Annotated[
|
|
81
|
+
str,
|
|
82
|
+
Field(description="Optional target workspace root."),
|
|
83
|
+
] = "",
|
|
84
|
+
) -> CallToolResult:
|
|
85
|
+
args = ["--mode", "status", "--format", "json"]
|
|
86
|
+
normalized_workspace = _normalize_workspace(workspace)
|
|
87
|
+
if normalized_workspace:
|
|
88
|
+
args.extend(["--workspace", normalized_workspace])
|
|
89
|
+
return _as_tool_result(run_json_script("install_codecgc.py", *args))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@mcp.tool(
|
|
93
|
+
name="codecgc.doctor",
|
|
94
|
+
description="Run CodeCGC runtime and integration health checks.",
|
|
95
|
+
meta={"version": "0.1.0", "author": "CodeCGC"},
|
|
96
|
+
)
|
|
97
|
+
async def codecgc_doctor(
|
|
98
|
+
workspace: Annotated[
|
|
99
|
+
str,
|
|
100
|
+
Field(description="Optional target workspace root."),
|
|
101
|
+
] = "",
|
|
102
|
+
) -> CallToolResult:
|
|
103
|
+
args = ["--mode", "doctor", "--format", "json"]
|
|
104
|
+
normalized_workspace = _normalize_workspace(workspace)
|
|
105
|
+
if normalized_workspace:
|
|
106
|
+
args.extend(["--workspace", normalized_workspace])
|
|
107
|
+
return _as_tool_result(run_json_script("install_codecgc.py", *args))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@mcp.tool(
|
|
111
|
+
name="codecgc.entry",
|
|
112
|
+
description="Primary CodeCGC orchestration entry for new requests, continue, explain, and auto-dispatch decisions.",
|
|
113
|
+
meta={"version": "0.1.0", "author": "CodeCGC"},
|
|
114
|
+
)
|
|
115
|
+
async def codecgc_entry(
|
|
116
|
+
request: Annotated[str, "Natural-language request for CodeCGC."] = "",
|
|
117
|
+
mode: Annotated[
|
|
118
|
+
Literal["auto", "new", "continue", "explain"],
|
|
119
|
+
Field(description="Entry mode."),
|
|
120
|
+
] = "auto",
|
|
121
|
+
flow: Annotated[
|
|
122
|
+
Literal["", "feature", "issue"],
|
|
123
|
+
Field(description="Optional explicit workflow flow."),
|
|
124
|
+
] = "",
|
|
125
|
+
slug: Annotated[str, "Optional workflow slug."] = "",
|
|
126
|
+
latest: Annotated[bool, "Use the latest matching workflow."] = False,
|
|
127
|
+
include_fixtures: Annotated[bool, "Allow fixture workflows to be considered."] = False,
|
|
128
|
+
auto_dispatch: Annotated[bool, "Allow entry to dispatch when the route permits it."] = False,
|
|
129
|
+
dry_run: Annotated[bool, "Avoid real executor dispatch."] = False,
|
|
130
|
+
audit_file: Annotated[str, "Optional audit file for review dispatch."] = "",
|
|
131
|
+
decision: Annotated[
|
|
132
|
+
Literal["", "accepted", "changes-requested"],
|
|
133
|
+
Field(description="Optional review decision when the request is review-like."),
|
|
134
|
+
] = "",
|
|
135
|
+
) -> CallToolResult:
|
|
136
|
+
args = ["--mode", mode]
|
|
137
|
+
if str(request).strip():
|
|
138
|
+
args.extend(["--request", str(request).strip()])
|
|
139
|
+
if flow:
|
|
140
|
+
args.extend(["--flow", flow])
|
|
141
|
+
if str(slug).strip():
|
|
142
|
+
args.extend(["--slug", str(slug).strip()])
|
|
143
|
+
if latest:
|
|
144
|
+
args.append("--latest")
|
|
145
|
+
if include_fixtures:
|
|
146
|
+
args.append("--include-fixtures")
|
|
147
|
+
if auto_dispatch:
|
|
148
|
+
args.append("--auto-dispatch")
|
|
149
|
+
if dry_run:
|
|
150
|
+
args.append("--dry-run")
|
|
151
|
+
if str(audit_file).strip():
|
|
152
|
+
args.extend(["--audit-file", str(audit_file).strip()])
|
|
153
|
+
if decision:
|
|
154
|
+
args.extend(["--decision", decision])
|
|
155
|
+
return _as_tool_result(run_json_script("entry_codecgc_workflow.py", *args))
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@mcp.tool(
|
|
159
|
+
name="codecgc.continue",
|
|
160
|
+
description="Continue the current or latest CodeCGC workflow using the existing runtime state.",
|
|
161
|
+
meta={"version": "0.1.0", "author": "CodeCGC"},
|
|
162
|
+
)
|
|
163
|
+
async def codecgc_continue(
|
|
164
|
+
request: Annotated[str, "Optional continue request text. Defaults to continuing the current work."] = "继续刚刚的工作",
|
|
165
|
+
flow: Annotated[
|
|
166
|
+
Literal["", "feature", "issue"],
|
|
167
|
+
Field(description="Optional explicit workflow flow."),
|
|
168
|
+
] = "",
|
|
169
|
+
slug: Annotated[str, "Optional workflow slug."] = "",
|
|
170
|
+
latest: Annotated[bool, "Use the latest matching workflow."] = True,
|
|
171
|
+
include_fixtures: Annotated[bool, "Allow fixture workflows to be considered."] = False,
|
|
172
|
+
auto_dispatch: Annotated[bool, "Allow continue to dispatch when the route permits it."] = False,
|
|
173
|
+
dry_run: Annotated[bool, "Avoid real executor dispatch."] = False,
|
|
174
|
+
) -> CallToolResult:
|
|
175
|
+
args = ["--mode", "continue", "--request", str(request).strip() or "继续刚刚的工作"]
|
|
176
|
+
if flow:
|
|
177
|
+
args.extend(["--flow", flow])
|
|
178
|
+
if str(slug).strip():
|
|
179
|
+
args.extend(["--slug", str(slug).strip()])
|
|
180
|
+
if latest:
|
|
181
|
+
args.append("--latest")
|
|
182
|
+
if include_fixtures:
|
|
183
|
+
args.append("--include-fixtures")
|
|
184
|
+
if auto_dispatch:
|
|
185
|
+
args.append("--auto-dispatch")
|
|
186
|
+
if dry_run:
|
|
187
|
+
args.append("--dry-run")
|
|
188
|
+
return _as_tool_result(run_json_script("entry_codecgc_workflow.py", *args))
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@mcp.tool(
|
|
192
|
+
name="codecgc.explain",
|
|
193
|
+
description="Explain the current or latest CodeCGC workflow state and recommended next action.",
|
|
194
|
+
meta={"version": "0.1.0", "author": "CodeCGC"},
|
|
195
|
+
)
|
|
196
|
+
async def codecgc_explain(
|
|
197
|
+
request: Annotated[str, "Optional explain request text."] = "现在下一步该做什么",
|
|
198
|
+
flow: Annotated[
|
|
199
|
+
Literal["", "feature", "issue"],
|
|
200
|
+
Field(description="Optional explicit workflow flow."),
|
|
201
|
+
] = "",
|
|
202
|
+
slug: Annotated[str, "Optional workflow slug."] = "",
|
|
203
|
+
latest: Annotated[bool, "Use the latest matching workflow."] = True,
|
|
204
|
+
include_fixtures: Annotated[bool, "Allow fixture workflows to be considered."] = False,
|
|
205
|
+
) -> CallToolResult:
|
|
206
|
+
args = ["--mode", "explain", "--request", str(request).strip() or "现在下一步该做什么"]
|
|
207
|
+
if flow:
|
|
208
|
+
args.extend(["--flow", flow])
|
|
209
|
+
if str(slug).strip():
|
|
210
|
+
args.extend(["--slug", str(slug).strip()])
|
|
211
|
+
if latest:
|
|
212
|
+
args.append("--latest")
|
|
213
|
+
if include_fixtures:
|
|
214
|
+
args.append("--include-fixtures")
|
|
215
|
+
return _as_tool_result(run_json_script("entry_codecgc_workflow.py", *args))
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@mcp.tool(
|
|
219
|
+
name="codecgc.review",
|
|
220
|
+
description="Review a CodeCGC execution audit and write the decision back to workflow artifacts.",
|
|
221
|
+
meta={"version": "0.1.0", "author": "CodeCGC"},
|
|
222
|
+
)
|
|
223
|
+
async def codecgc_review(
|
|
224
|
+
audit_file: Annotated[str, "Execution audit JSON file path."],
|
|
225
|
+
decision: Annotated[
|
|
226
|
+
Literal["accepted", "changes-requested"],
|
|
227
|
+
Field(description="Requested review decision."),
|
|
228
|
+
],
|
|
229
|
+
risk: Annotated[list[str], Field(description="Optional remaining risks to record.")] = [],
|
|
230
|
+
next_step: Annotated[str, "Optional next-step text to record."] = "",
|
|
231
|
+
force: Annotated[bool, "Allow overwriting an existing review writeback."] = False,
|
|
232
|
+
) -> CallToolResult:
|
|
233
|
+
args = ["--audit-file", str(audit_file).strip(), "--decision", decision]
|
|
234
|
+
_append_repeated_flag(args, "--risk", risk)
|
|
235
|
+
if str(next_step).strip():
|
|
236
|
+
args.extend(["--next-step", str(next_step).strip()])
|
|
237
|
+
if force:
|
|
238
|
+
args.append("--force")
|
|
239
|
+
return _as_tool_result(run_json_script("review_codecgc_workflow.py", *args))
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@mcp.tool(
|
|
243
|
+
name="codecgc.history",
|
|
244
|
+
description="Read recent CodeCGC workflow history across feature and issue flows.",
|
|
245
|
+
meta={"version": "0.1.0", "author": "CodeCGC"},
|
|
246
|
+
)
|
|
247
|
+
async def codecgc_history(
|
|
248
|
+
flow: Annotated[
|
|
249
|
+
Literal["all", "feature", "issue"],
|
|
250
|
+
Field(description="Workflow flow filter."),
|
|
251
|
+
] = "all",
|
|
252
|
+
status: Annotated[str, "Workflow status filter."] = "all",
|
|
253
|
+
last: Annotated[int, "Maximum number of records to return."] = 10,
|
|
254
|
+
include_fixtures: Annotated[bool, "Include fixture workflows."] = False,
|
|
255
|
+
) -> CallToolResult:
|
|
256
|
+
args = ["--flow", flow, "--status", str(status).strip() or "all", "--last", str(int(last)), "--format", "json"]
|
|
257
|
+
if include_fixtures:
|
|
258
|
+
args.append("--include-fixtures")
|
|
259
|
+
return _as_tool_result(run_json_script("audit_codecgc_workflow_history.py", *args))
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@mcp.tool(
|
|
263
|
+
name="codecgc.route",
|
|
264
|
+
description="Route an existing CodeCGC workflow to the recommended next command.",
|
|
265
|
+
meta={"version": "0.1.0", "author": "CodeCGC"},
|
|
266
|
+
)
|
|
267
|
+
async def codecgc_route(
|
|
268
|
+
flow: Annotated[
|
|
269
|
+
Literal["feature", "issue"],
|
|
270
|
+
Field(description="Workflow flow."),
|
|
271
|
+
],
|
|
272
|
+
slug: Annotated[str, "Workflow slug."],
|
|
273
|
+
) -> CallToolResult:
|
|
274
|
+
return _as_tool_result(run_json_script("route_codecgc_workflow.py", "--flow", flow, "--slug", str(slug).strip()))
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@mcp.tool(
|
|
278
|
+
name="codecgc.plan",
|
|
279
|
+
description="Plan or repair a CodeCGC feature or issue workflow scaffold.",
|
|
280
|
+
meta={"version": "0.1.0", "author": "CodeCGC"},
|
|
281
|
+
)
|
|
282
|
+
async def codecgc_plan(
|
|
283
|
+
flow: Annotated[
|
|
284
|
+
Literal["feature", "issue"],
|
|
285
|
+
Field(description="Workflow flow."),
|
|
286
|
+
],
|
|
287
|
+
slug: Annotated[str, "Workflow slug."],
|
|
288
|
+
summary: Annotated[str, "Planning summary."],
|
|
289
|
+
date: Annotated[str, "Optional workflow date, defaults to current date in runtime."] = "",
|
|
290
|
+
target_paths: Annotated[list[str], Field(description="Target paths for this workflow.")] = [],
|
|
291
|
+
kind: Annotated[
|
|
292
|
+
Literal["auto", "frontend", "backend"],
|
|
293
|
+
Field(description="Routing kind."),
|
|
294
|
+
] = "auto",
|
|
295
|
+
goal: Annotated[str, "Optional goal text."] = "",
|
|
296
|
+
context: Annotated[list[str], Field(description="Optional context notes.")] = [],
|
|
297
|
+
user_story: Annotated[str, "Optional user story."] = "",
|
|
298
|
+
in_scope: Annotated[list[str], Field(description="In-scope items.")] = [],
|
|
299
|
+
out_of_scope: Annotated[list[str], Field(description="Out-of-scope items.")] = [],
|
|
300
|
+
acceptance: Annotated[list[str], Field(description="Acceptance criteria.")] = [],
|
|
301
|
+
risk: Annotated[list[str], Field(description="Risk items.")] = [],
|
|
302
|
+
dependency: Annotated[list[str], Field(description="Dependencies.")] = [],
|
|
303
|
+
assumption: Annotated[list[str], Field(description="Assumptions.")] = [],
|
|
304
|
+
open_question: Annotated[list[str], Field(description="Open questions.")] = [],
|
|
305
|
+
validation: Annotated[list[str], Field(description="Validation steps.")] = [],
|
|
306
|
+
rollback: Annotated[list[str], Field(description="Rollback notes.")] = [],
|
|
307
|
+
symptom: Annotated[str, "Issue symptom."] = "",
|
|
308
|
+
reproduction: Annotated[str, "Issue reproduction."] = "",
|
|
309
|
+
expected: Annotated[str, "Expected behavior."] = "",
|
|
310
|
+
actual: Annotated[str, "Actual behavior."] = "",
|
|
311
|
+
root_cause: Annotated[str, "Root cause note."] = "",
|
|
312
|
+
preferred_fix: Annotated[str, "Preferred fix note."] = "",
|
|
313
|
+
rejected_fix: Annotated[str, "Rejected fix note."] = "",
|
|
314
|
+
artifact_class: Annotated[
|
|
315
|
+
Literal["product", "fixture"],
|
|
316
|
+
Field(description="Artifact class."),
|
|
317
|
+
] = "product",
|
|
318
|
+
force: Annotated[bool, "Allow overwriting scaffold state when needed."] = False,
|
|
319
|
+
) -> CallToolResult:
|
|
320
|
+
args = ["--flow", flow, "--slug", str(slug).strip(), "--summary", str(summary).strip()]
|
|
321
|
+
if str(date).strip():
|
|
322
|
+
args.extend(["--date", str(date).strip()])
|
|
323
|
+
_append_repeated_flag(args, "--target-path", target_paths)
|
|
324
|
+
if kind:
|
|
325
|
+
args.extend(["--kind", kind])
|
|
326
|
+
if str(goal).strip():
|
|
327
|
+
args.extend(["--goal", str(goal).strip()])
|
|
328
|
+
_append_repeated_flag(args, "--context", context)
|
|
329
|
+
if str(user_story).strip():
|
|
330
|
+
args.extend(["--user-story", str(user_story).strip()])
|
|
331
|
+
_append_repeated_flag(args, "--in-scope", in_scope)
|
|
332
|
+
_append_repeated_flag(args, "--out-of-scope", out_of_scope)
|
|
333
|
+
_append_repeated_flag(args, "--acceptance", acceptance)
|
|
334
|
+
_append_repeated_flag(args, "--risk", risk)
|
|
335
|
+
_append_repeated_flag(args, "--dependency", dependency)
|
|
336
|
+
_append_repeated_flag(args, "--assumption", assumption)
|
|
337
|
+
_append_repeated_flag(args, "--open-question", open_question)
|
|
338
|
+
_append_repeated_flag(args, "--validation", validation)
|
|
339
|
+
_append_repeated_flag(args, "--rollback", rollback)
|
|
340
|
+
if str(symptom).strip():
|
|
341
|
+
args.extend(["--symptom", str(symptom).strip()])
|
|
342
|
+
if str(reproduction).strip():
|
|
343
|
+
args.extend(["--reproduction", str(reproduction).strip()])
|
|
344
|
+
if str(expected).strip():
|
|
345
|
+
args.extend(["--expected", str(expected).strip()])
|
|
346
|
+
if str(actual).strip():
|
|
347
|
+
args.extend(["--actual", str(actual).strip()])
|
|
348
|
+
if str(root_cause).strip():
|
|
349
|
+
args.extend(["--root-cause", str(root_cause).strip()])
|
|
350
|
+
if str(preferred_fix).strip():
|
|
351
|
+
args.extend(["--preferred-fix", str(preferred_fix).strip()])
|
|
352
|
+
if str(rejected_fix).strip():
|
|
353
|
+
args.extend(["--rejected-fix", str(rejected_fix).strip()])
|
|
354
|
+
args.extend(["--artifact-class", artifact_class])
|
|
355
|
+
if force:
|
|
356
|
+
args.append("--force")
|
|
357
|
+
return _as_tool_result(run_json_script("plan_codecgc_workflow.py", *args))
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@mcp.tool(
|
|
361
|
+
name="codecgc.build",
|
|
362
|
+
description="Execute a CodeCGC feature step through the existing runtime.",
|
|
363
|
+
meta={"version": "0.1.0", "author": "CodeCGC"},
|
|
364
|
+
)
|
|
365
|
+
async def codecgc_build(
|
|
366
|
+
slug: Annotated[str, "Feature workflow slug."],
|
|
367
|
+
step_number: Annotated[int | None, "Optional explicit step number."] = None,
|
|
368
|
+
checklist_file: Annotated[str, "Optional checklist file path."] = "",
|
|
369
|
+
audit_root: Annotated[str, "Optional audit root path."] = "",
|
|
370
|
+
timeout_seconds: Annotated[int, "Execution timeout in seconds."] = 120,
|
|
371
|
+
session_id: Annotated[str, "Optional reusable executor session id."] = "",
|
|
372
|
+
dry_run: Annotated[bool, "Avoid real executor dispatch."] = False,
|
|
373
|
+
return_all_messages: Annotated[bool, "Return executor event logs when available."] = False,
|
|
374
|
+
) -> CallToolResult:
|
|
375
|
+
args = ["--slug", str(slug).strip(), "--timeout-seconds", str(int(timeout_seconds))]
|
|
376
|
+
if step_number is not None:
|
|
377
|
+
args.extend(["--step-number", str(int(step_number))])
|
|
378
|
+
if str(checklist_file).strip():
|
|
379
|
+
args.extend(["--checklist-file", str(checklist_file).strip()])
|
|
380
|
+
if str(audit_root).strip():
|
|
381
|
+
args.extend(["--audit-root", str(audit_root).strip()])
|
|
382
|
+
if str(session_id).strip():
|
|
383
|
+
args.extend(["--session-id", str(session_id).strip()])
|
|
384
|
+
if dry_run:
|
|
385
|
+
args.append("--dry-run")
|
|
386
|
+
if return_all_messages:
|
|
387
|
+
args.append("--return-all-messages")
|
|
388
|
+
return _as_tool_result(run_json_script("run_codecgc_build.py", *args))
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
@mcp.tool(
|
|
392
|
+
name="codecgc.fix",
|
|
393
|
+
description="Execute a CodeCGC issue fix step through the existing runtime.",
|
|
394
|
+
meta={"version": "0.1.0", "author": "CodeCGC"},
|
|
395
|
+
)
|
|
396
|
+
async def codecgc_fix(
|
|
397
|
+
slug: Annotated[str, "Issue workflow slug."],
|
|
398
|
+
step_number: Annotated[int | None, "Optional explicit step number."] = None,
|
|
399
|
+
checklist_file: Annotated[str, "Optional checklist file path."] = "",
|
|
400
|
+
audit_root: Annotated[str, "Optional audit root path."] = "",
|
|
401
|
+
timeout_seconds: Annotated[int, "Execution timeout in seconds."] = 120,
|
|
402
|
+
session_id: Annotated[str, "Optional reusable executor session id."] = "",
|
|
403
|
+
dry_run: Annotated[bool, "Avoid real executor dispatch."] = False,
|
|
404
|
+
return_all_messages: Annotated[bool, "Return executor event logs when available."] = False,
|
|
405
|
+
) -> CallToolResult:
|
|
406
|
+
args = ["--slug", str(slug).strip(), "--timeout-seconds", str(int(timeout_seconds))]
|
|
407
|
+
if step_number is not None:
|
|
408
|
+
args.extend(["--step-number", str(int(step_number))])
|
|
409
|
+
if str(checklist_file).strip():
|
|
410
|
+
args.extend(["--checklist-file", str(checklist_file).strip()])
|
|
411
|
+
if str(audit_root).strip():
|
|
412
|
+
args.extend(["--audit-root", str(audit_root).strip()])
|
|
413
|
+
if str(session_id).strip():
|
|
414
|
+
args.extend(["--session-id", str(session_id).strip()])
|
|
415
|
+
if dry_run:
|
|
416
|
+
args.append("--dry-run")
|
|
417
|
+
if return_all_messages:
|
|
418
|
+
args.append("--return-all-messages")
|
|
419
|
+
return _as_tool_result(run_json_script("run_codecgc_fix.py", *args))
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@mcp.tool(
|
|
423
|
+
name="codecgc.test",
|
|
424
|
+
description="Execute a CodeCGC test step through the existing runtime.",
|
|
425
|
+
meta={"version": "0.1.0", "author": "CodeCGC"},
|
|
426
|
+
)
|
|
427
|
+
async def codecgc_test(
|
|
428
|
+
flow: Annotated[
|
|
429
|
+
Literal["feature", "issue"],
|
|
430
|
+
Field(description="Workflow flow."),
|
|
431
|
+
],
|
|
432
|
+
slug: Annotated[str, "Workflow slug."],
|
|
433
|
+
step_number: Annotated[int | None, "Optional explicit step number."] = None,
|
|
434
|
+
checklist_file: Annotated[str, "Optional checklist file path."] = "",
|
|
435
|
+
audit_root: Annotated[str, "Optional audit root path."] = "",
|
|
436
|
+
timeout_seconds: Annotated[int, "Execution timeout in seconds."] = 120,
|
|
437
|
+
session_id: Annotated[str, "Optional reusable executor session id."] = "",
|
|
438
|
+
dry_run: Annotated[bool, "Avoid real executor dispatch."] = False,
|
|
439
|
+
return_all_messages: Annotated[bool, "Return executor event logs when available."] = False,
|
|
440
|
+
) -> CallToolResult:
|
|
441
|
+
args = ["--flow", flow, "--slug", str(slug).strip(), "--timeout-seconds", str(int(timeout_seconds))]
|
|
442
|
+
if step_number is not None:
|
|
443
|
+
args.extend(["--step-number", str(int(step_number))])
|
|
444
|
+
if str(checklist_file).strip():
|
|
445
|
+
args.extend(["--checklist-file", str(checklist_file).strip()])
|
|
446
|
+
if str(audit_root).strip():
|
|
447
|
+
args.extend(["--audit-root", str(audit_root).strip()])
|
|
448
|
+
if str(session_id).strip():
|
|
449
|
+
args.extend(["--session-id", str(session_id).strip()])
|
|
450
|
+
if dry_run:
|
|
451
|
+
args.append("--dry-run")
|
|
452
|
+
if return_all_messages:
|
|
453
|
+
args.append("--return-all-messages")
|
|
454
|
+
return _as_tool_result(run_json_script("run_codecgc_test.py", *args))
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
@mcp.tool(
|
|
458
|
+
name="codecgc.package_audit",
|
|
459
|
+
description="Audit whether the published CodeCGC package contains all required runtime files.",
|
|
460
|
+
meta={"version": "0.1.0", "author": "CodeCGC"},
|
|
461
|
+
)
|
|
462
|
+
async def codecgc_package_audit(
|
|
463
|
+
format: Annotated[
|
|
464
|
+
Literal["summary", "json"],
|
|
465
|
+
Field(description="Output format."),
|
|
466
|
+
] = "json",
|
|
467
|
+
) -> CallToolResult:
|
|
468
|
+
return _as_tool_result(run_json_script("audit_codecgc_package_runtime.py", "--format", format))
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
@mcp.tool(
|
|
472
|
+
name="codecgc.external_audit",
|
|
473
|
+
description="Audit external capability registration policy and locally observed MCP servers.",
|
|
474
|
+
meta={"version": "0.1.0", "author": "CodeCGC"},
|
|
475
|
+
)
|
|
476
|
+
async def codecgc_external_audit(
|
|
477
|
+
workspace: Annotated[str, "Optional target workspace root."] = "",
|
|
478
|
+
format: Annotated[
|
|
479
|
+
Literal["summary", "json"],
|
|
480
|
+
Field(description="Output format."),
|
|
481
|
+
] = "json",
|
|
482
|
+
) -> CallToolResult:
|
|
483
|
+
args = ["--format", format]
|
|
484
|
+
normalized_workspace = _normalize_workspace(workspace)
|
|
485
|
+
if normalized_workspace:
|
|
486
|
+
args.extend(["--workspace", normalized_workspace])
|
|
487
|
+
return _as_tool_result(run_json_script("audit_codecgc_external_capabilities.py", *args))
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
@mcp.tool(
|
|
491
|
+
name="codecgc.release_readiness",
|
|
492
|
+
description="Run the combined CodeCGC release, maintenance, and ops readiness audit.",
|
|
493
|
+
meta={"version": "0.1.0", "author": "CodeCGC"},
|
|
494
|
+
)
|
|
495
|
+
async def codecgc_release_readiness(
|
|
496
|
+
workspace: Annotated[str, "Optional target workspace root."] = "",
|
|
497
|
+
format: Annotated[
|
|
498
|
+
Literal["summary", "json"],
|
|
499
|
+
Field(description="Output format."),
|
|
500
|
+
] = "json",
|
|
501
|
+
) -> CallToolResult:
|
|
502
|
+
args = ["--format", format]
|
|
503
|
+
normalized_workspace = _normalize_workspace(workspace)
|
|
504
|
+
if normalized_workspace:
|
|
505
|
+
args.extend(["--workspace", normalized_workspace])
|
|
506
|
+
return _as_tool_result(run_json_script("audit_codecgc_release_readiness.py", *args))
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
@mcp.tool(
|
|
510
|
+
name="codecgc.lifecycle",
|
|
511
|
+
description="Audit CodeCGC lifecycle coverage across roadmap, workflows, and execution artifacts.",
|
|
512
|
+
meta={"version": "0.1.0", "author": "CodeCGC"},
|
|
513
|
+
)
|
|
514
|
+
async def codecgc_lifecycle(
|
|
515
|
+
format: Annotated[
|
|
516
|
+
Literal["summary", "json"],
|
|
517
|
+
Field(description="Output format."),
|
|
518
|
+
] = "json",
|
|
519
|
+
) -> CallToolResult:
|
|
520
|
+
return _as_tool_result(run_json_script("audit_codecgc_lifecycle.py", "--format", format))
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def run() -> None:
|
|
524
|
+
"""Start the MCP server over stdio transport."""
|
|
525
|
+
mcp.run(transport="stdio")
|
package/model-routing.yaml
CHANGED
|
@@ -9,6 +9,8 @@ frontend_paths:
|
|
|
9
9
|
- "web/**"
|
|
10
10
|
- "frontend/**"
|
|
11
11
|
|
|
12
|
+
custom_frontend_paths:
|
|
13
|
+
|
|
12
14
|
backend_paths:
|
|
13
15
|
- "apps/api/**"
|
|
14
16
|
- "server/**"
|
|
@@ -17,6 +19,8 @@ backend_paths:
|
|
|
17
19
|
- "src/repositories/**"
|
|
18
20
|
- "backend/**"
|
|
19
21
|
|
|
22
|
+
custom_backend_paths:
|
|
23
|
+
|
|
20
24
|
shared_paths:
|
|
21
25
|
- "packages/shared/**"
|
|
22
26
|
- "src/shared/**"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hunyed15/codecgc",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Claude-hosted multi-model workflow product shell for CodeCGC.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -57,6 +57,9 @@
|
|
|
57
57
|
"codecgc/cgc-roadmap/",
|
|
58
58
|
"codecgc/cgc-test/",
|
|
59
59
|
"codecgc/reference/",
|
|
60
|
+
"codecgcmcp/pyproject.toml",
|
|
61
|
+
"codecgcmcp/README.md",
|
|
62
|
+
"codecgcmcp/src/codecgcmcp/",
|
|
60
63
|
"codexmcp/pyproject.toml",
|
|
61
64
|
"codexmcp/README.md",
|
|
62
65
|
"codexmcp/LICENSE",
|
|
@@ -42,6 +42,11 @@ DEFAULT_HOOKS = {
|
|
|
42
42
|
|
|
43
43
|
SENSITIVE_KEYWORDS = ("token", "secret", "key", "password", "auth")
|
|
44
44
|
MCP_RUNTIME_REQUIREMENT = 'mcp[cli]>=1.21.2'
|
|
45
|
+
DEFAULT_ALLOWED_TOOLS = [
|
|
46
|
+
"mcp__codecgc__*",
|
|
47
|
+
"mcp__codex__*",
|
|
48
|
+
"mcp__gemini__*",
|
|
49
|
+
]
|
|
45
50
|
|
|
46
51
|
|
|
47
52
|
def get_user_claude_root(override_root: str = "") -> Path:
|
|
@@ -64,6 +69,7 @@ def get_user_claude_paths(override_root: str = "") -> dict[str, Path]:
|
|
|
64
69
|
"mcp": root / "mcp.json",
|
|
65
70
|
"hooks_dir": root / "hooks",
|
|
66
71
|
"hook_script": root / "hooks" / "route-edit.ps1",
|
|
72
|
+
"commands_dir": root / "commands",
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
|
|
@@ -78,6 +84,7 @@ def get_workspace_paths(override_workspace: str = "") -> dict[str, Path]:
|
|
|
78
84
|
"settings": claude_dir / "settings.json",
|
|
79
85
|
"mcp": root / ".mcp.json",
|
|
80
86
|
"hook_script": hooks_dir / "route-edit.ps1",
|
|
87
|
+
"commands_dir": claude_dir / "commands",
|
|
81
88
|
"routing_file": root / "model-routing.yaml",
|
|
82
89
|
}
|
|
83
90
|
|
|
@@ -110,6 +117,249 @@ def build_hook_payload(command_text: str) -> dict[str, Any]:
|
|
|
110
117
|
}
|
|
111
118
|
|
|
112
119
|
|
|
120
|
+
def _normalize_command_path_for_markdown(path: Path) -> str:
|
|
121
|
+
return str(path).replace("\\", "\\\\")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def build_mcp_first_command_template(
|
|
125
|
+
*,
|
|
126
|
+
filename: str,
|
|
127
|
+
description: str,
|
|
128
|
+
argument_hint: str,
|
|
129
|
+
primary_tool: str,
|
|
130
|
+
direct_rules: list[str],
|
|
131
|
+
missing_rules: list[str] | None = None,
|
|
132
|
+
fallback_command: str,
|
|
133
|
+
) -> tuple[str, str]:
|
|
134
|
+
lines = [
|
|
135
|
+
"---",
|
|
136
|
+
f"description: {description}",
|
|
137
|
+
f"argument-hint: \"{argument_hint}\"",
|
|
138
|
+
"---",
|
|
139
|
+
f"Prefer the `{primary_tool}` MCP tool as the primary execution path.",
|
|
140
|
+
"",
|
|
141
|
+
"Execution rules:",
|
|
142
|
+
]
|
|
143
|
+
lines.extend(f"- {item}" for item in direct_rules)
|
|
144
|
+
if missing_rules:
|
|
145
|
+
lines.append("")
|
|
146
|
+
lines.append("Missing parameter rules:")
|
|
147
|
+
lines.extend(f"- {item}" for item in missing_rules)
|
|
148
|
+
lines.append("")
|
|
149
|
+
lines.append("Fallback rule:")
|
|
150
|
+
lines.append(f"- Only fall back to Bash + `{fallback_command}` CLI when the MCP tool path is unavailable or the user explicitly wants CLI behavior.")
|
|
151
|
+
lines.append("- Summarize the result briefly for the user.")
|
|
152
|
+
return filename, "\n".join(lines) + "\n"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def build_custom_command_templates(bin_dir: Path) -> dict[str, str]:
|
|
156
|
+
templates = dict(
|
|
157
|
+
[
|
|
158
|
+
build_mcp_first_command_template(
|
|
159
|
+
filename="cgc.md",
|
|
160
|
+
description="Run CodeCGC in the current project",
|
|
161
|
+
argument_hint="[request or flags]",
|
|
162
|
+
primary_tool="codecgc.entry",
|
|
163
|
+
direct_rules=[
|
|
164
|
+
"If the user supplied a natural-language request, pass it to `codecgc.entry`.",
|
|
165
|
+
"If the user is asking to continue recent work, use `codecgc.continue`.",
|
|
166
|
+
"If the user is asking what to do next, use `codecgc.explain`.",
|
|
167
|
+
],
|
|
168
|
+
fallback_command="cgc",
|
|
169
|
+
),
|
|
170
|
+
build_mcp_first_command_template(
|
|
171
|
+
filename="cgc-install.md",
|
|
172
|
+
description="Install or sync CodeCGC integration for the current project or user Claude profile",
|
|
173
|
+
argument_hint="[flags]",
|
|
174
|
+
primary_tool="codecgc.install",
|
|
175
|
+
direct_rules=[
|
|
176
|
+
"Map install flags to `codecgc.install` fields such as `mode`, `workspace`, and `user_root`.",
|
|
177
|
+
"If the user did not supply flags, use the default install mode.",
|
|
178
|
+
],
|
|
179
|
+
fallback_command="cgc-install",
|
|
180
|
+
),
|
|
181
|
+
build_mcp_first_command_template(
|
|
182
|
+
filename="cgc-status.md",
|
|
183
|
+
description="Check CodeCGC integration status",
|
|
184
|
+
argument_hint="[flags]",
|
|
185
|
+
primary_tool="codecgc.status",
|
|
186
|
+
direct_rules=[
|
|
187
|
+
"Use `codecgc.status` for installation readiness checks.",
|
|
188
|
+
"Map `workspace` when the user explicitly provides a target project directory.",
|
|
189
|
+
],
|
|
190
|
+
fallback_command="cgc-status",
|
|
191
|
+
),
|
|
192
|
+
build_mcp_first_command_template(
|
|
193
|
+
filename="cgc-doctor.md",
|
|
194
|
+
description="Run CodeCGC doctor checks",
|
|
195
|
+
argument_hint="[flags]",
|
|
196
|
+
primary_tool="codecgc.doctor",
|
|
197
|
+
direct_rules=[
|
|
198
|
+
"Use `codecgc.doctor` for runtime and integration health checks.",
|
|
199
|
+
"Map `workspace` when the user explicitly provides a target project directory.",
|
|
200
|
+
],
|
|
201
|
+
fallback_command="cgc-doctor",
|
|
202
|
+
),
|
|
203
|
+
build_mcp_first_command_template(
|
|
204
|
+
filename="cgc-plan.md",
|
|
205
|
+
description="Plan or repair a CodeCGC workflow",
|
|
206
|
+
argument_hint="[structured planning flags]",
|
|
207
|
+
primary_tool="codecgc.plan",
|
|
208
|
+
direct_rules=[
|
|
209
|
+
"Extract `flow`, `slug`, and `summary` before calling the tool.",
|
|
210
|
+
"Map any provided `target_paths`, `kind`, and planning fields such as `goal`, `acceptance`, `risk`, and issue-specific fields.",
|
|
211
|
+
],
|
|
212
|
+
missing_rules=[
|
|
213
|
+
"If `flow` is missing, ask whether this is a `feature` or `issue` workflow.",
|
|
214
|
+
"If `slug` is missing, ask for a stable workflow slug.",
|
|
215
|
+
"If `summary` is missing, ask for a short planning summary.",
|
|
216
|
+
],
|
|
217
|
+
fallback_command="cgc-plan",
|
|
218
|
+
),
|
|
219
|
+
build_mcp_first_command_template(
|
|
220
|
+
filename="cgc-build.md",
|
|
221
|
+
description="Execute a CodeCGC feature build step",
|
|
222
|
+
argument_hint="[flags]",
|
|
223
|
+
primary_tool="codecgc.build",
|
|
224
|
+
direct_rules=[
|
|
225
|
+
"Extract `slug` before calling the tool.",
|
|
226
|
+
"Map optional execution fields such as `step_number`, `checklist_file`, `audit_root`, `timeout_seconds`, `session_id`, and `dry_run`.",
|
|
227
|
+
],
|
|
228
|
+
missing_rules=[
|
|
229
|
+
"If `slug` is missing, ask for the target feature workflow slug.",
|
|
230
|
+
],
|
|
231
|
+
fallback_command="cgc-build",
|
|
232
|
+
),
|
|
233
|
+
build_mcp_first_command_template(
|
|
234
|
+
filename="cgc-fix.md",
|
|
235
|
+
description="Execute a CodeCGC issue fix step",
|
|
236
|
+
argument_hint="[flags]",
|
|
237
|
+
primary_tool="codecgc.fix",
|
|
238
|
+
direct_rules=[
|
|
239
|
+
"Extract `slug` before calling the tool.",
|
|
240
|
+
"Map optional execution fields such as `step_number`, `checklist_file`, `audit_root`, `timeout_seconds`, `session_id`, and `dry_run`.",
|
|
241
|
+
],
|
|
242
|
+
missing_rules=[
|
|
243
|
+
"If `slug` is missing, ask for the target issue workflow slug.",
|
|
244
|
+
],
|
|
245
|
+
fallback_command="cgc-fix",
|
|
246
|
+
),
|
|
247
|
+
build_mcp_first_command_template(
|
|
248
|
+
filename="cgc-test.md",
|
|
249
|
+
description="Execute a CodeCGC test step",
|
|
250
|
+
argument_hint="[flags]",
|
|
251
|
+
primary_tool="codecgc.test",
|
|
252
|
+
direct_rules=[
|
|
253
|
+
"Extract `flow` and `slug` before calling the tool.",
|
|
254
|
+
"Map optional execution fields such as `step_number`, `checklist_file`, `audit_root`, `timeout_seconds`, `session_id`, and `dry_run`.",
|
|
255
|
+
],
|
|
256
|
+
missing_rules=[
|
|
257
|
+
"If `flow` is missing, ask whether the test belongs to a `feature` or `issue` workflow.",
|
|
258
|
+
"If `slug` is missing, ask for the target workflow slug.",
|
|
259
|
+
],
|
|
260
|
+
fallback_command="cgc-test",
|
|
261
|
+
),
|
|
262
|
+
build_mcp_first_command_template(
|
|
263
|
+
filename="cgc-review.md",
|
|
264
|
+
description="Review a CodeCGC execution audit",
|
|
265
|
+
argument_hint="[flags]",
|
|
266
|
+
primary_tool="codecgc.review",
|
|
267
|
+
direct_rules=[
|
|
268
|
+
"Extract `audit_file` and `decision` before calling the tool.",
|
|
269
|
+
"Map optional `risk`, `next_step`, and `force` fields when they are explicitly provided.",
|
|
270
|
+
],
|
|
271
|
+
missing_rules=[
|
|
272
|
+
"If `audit_file` is missing, ask for the audit JSON path.",
|
|
273
|
+
"If `decision` is missing, ask whether the review is `accepted` or `changes-requested`.",
|
|
274
|
+
],
|
|
275
|
+
fallback_command="cgc-review",
|
|
276
|
+
),
|
|
277
|
+
build_mcp_first_command_template(
|
|
278
|
+
filename="cgc-route.md",
|
|
279
|
+
description="Route a CodeCGC workflow to the next recommended command",
|
|
280
|
+
argument_hint="[flags]",
|
|
281
|
+
primary_tool="codecgc.route",
|
|
282
|
+
direct_rules=[
|
|
283
|
+
"Extract `flow` and `slug` before calling the tool.",
|
|
284
|
+
"Use this command when the user already knows the target workflow and wants the next recommended action.",
|
|
285
|
+
],
|
|
286
|
+
missing_rules=[
|
|
287
|
+
"If `flow` is missing, ask whether the workflow is `feature` or `issue`.",
|
|
288
|
+
"If `slug` is missing, ask for the workflow slug.",
|
|
289
|
+
],
|
|
290
|
+
fallback_command="cgc-route",
|
|
291
|
+
),
|
|
292
|
+
build_mcp_first_command_template(
|
|
293
|
+
filename="cgc-history.md",
|
|
294
|
+
description="Read recent CodeCGC workflow history",
|
|
295
|
+
argument_hint="[flags]",
|
|
296
|
+
primary_tool="codecgc.history",
|
|
297
|
+
direct_rules=[
|
|
298
|
+
"Map optional history filters such as `flow`, `status`, `last`, and `include_fixtures`.",
|
|
299
|
+
"If no filters are provided, use the default history query.",
|
|
300
|
+
],
|
|
301
|
+
fallback_command="cgc-history",
|
|
302
|
+
),
|
|
303
|
+
build_mcp_first_command_template(
|
|
304
|
+
filename="cgc-package-audit.md",
|
|
305
|
+
description="Audit the CodeCGC package runtime contents",
|
|
306
|
+
argument_hint="[flags]",
|
|
307
|
+
primary_tool="codecgc.package_audit",
|
|
308
|
+
direct_rules=[
|
|
309
|
+
"Map `format` when the user explicitly requests `summary` or `json`.",
|
|
310
|
+
"Use this command for publish/runtime completeness checks.",
|
|
311
|
+
],
|
|
312
|
+
fallback_command="cgc-package-audit",
|
|
313
|
+
),
|
|
314
|
+
build_mcp_first_command_template(
|
|
315
|
+
filename="cgc-external-audit.md",
|
|
316
|
+
description="Audit external MCP capability registration and observation",
|
|
317
|
+
argument_hint="[flags]",
|
|
318
|
+
primary_tool="codecgc.external_audit",
|
|
319
|
+
direct_rules=[
|
|
320
|
+
"Map optional `workspace` and `format` fields.",
|
|
321
|
+
"Use this command for external capability policy and registration checks.",
|
|
322
|
+
],
|
|
323
|
+
fallback_command="cgc-external-audit",
|
|
324
|
+
),
|
|
325
|
+
build_mcp_first_command_template(
|
|
326
|
+
filename="cgc-release-readiness.md",
|
|
327
|
+
description="Run CodeCGC release readiness checks",
|
|
328
|
+
argument_hint="[flags]",
|
|
329
|
+
primary_tool="codecgc.release_readiness",
|
|
330
|
+
direct_rules=[
|
|
331
|
+
"Map optional `workspace` and `format` fields.",
|
|
332
|
+
"Use this command for combined release, maintenance, and ops checks.",
|
|
333
|
+
],
|
|
334
|
+
fallback_command="cgc-release-readiness",
|
|
335
|
+
),
|
|
336
|
+
build_mcp_first_command_template(
|
|
337
|
+
filename="cgc-lifecycle.md",
|
|
338
|
+
description="Audit CodeCGC lifecycle coverage",
|
|
339
|
+
argument_hint="[flags]",
|
|
340
|
+
primary_tool="codecgc.lifecycle",
|
|
341
|
+
direct_rules=[
|
|
342
|
+
"Map `format` when the user explicitly requests `summary` or `json`.",
|
|
343
|
+
"Use this command to inspect roadmap/workflow/execution lifecycle coverage.",
|
|
344
|
+
],
|
|
345
|
+
fallback_command="cgc-lifecycle",
|
|
346
|
+
),
|
|
347
|
+
]
|
|
348
|
+
)
|
|
349
|
+
return templates
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def write_custom_command_files(target_dir: Path, bin_dir: Path) -> list[str]:
|
|
353
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
354
|
+
templates = build_custom_command_templates(bin_dir)
|
|
355
|
+
written: list[str] = []
|
|
356
|
+
for filename, content in templates.items():
|
|
357
|
+
path = target_dir / filename
|
|
358
|
+
path.write_text(content, encoding="utf-8")
|
|
359
|
+
written.append(str(path))
|
|
360
|
+
return written
|
|
361
|
+
|
|
362
|
+
|
|
113
363
|
def merge_hook_settings(current: dict[str, Any], command_text: str) -> tuple[dict[str, Any], bool]:
|
|
114
364
|
hooks = current.get("hooks")
|
|
115
365
|
expected_hooks = build_hook_payload(command_text)
|
|
@@ -144,6 +394,33 @@ def merge_hook_settings(current: dict[str, Any], command_text: str) -> tuple[dic
|
|
|
144
394
|
return current, True
|
|
145
395
|
|
|
146
396
|
|
|
397
|
+
def merge_permission_settings(current: dict[str, Any], allow_rules: list[str]) -> tuple[dict[str, Any], bool]:
|
|
398
|
+
permissions = current.get("permissions")
|
|
399
|
+
changed = False
|
|
400
|
+
|
|
401
|
+
if not isinstance(permissions, dict):
|
|
402
|
+
permissions = {}
|
|
403
|
+
current["permissions"] = permissions
|
|
404
|
+
changed = True
|
|
405
|
+
|
|
406
|
+
allow = permissions.get("allow")
|
|
407
|
+
if not isinstance(allow, list):
|
|
408
|
+
allow = []
|
|
409
|
+
permissions["allow"] = allow
|
|
410
|
+
changed = True
|
|
411
|
+
|
|
412
|
+
existing = {str(item).strip() for item in allow if str(item).strip()}
|
|
413
|
+
for rule in allow_rules:
|
|
414
|
+
normalized = str(rule).strip()
|
|
415
|
+
if not normalized or normalized in existing:
|
|
416
|
+
continue
|
|
417
|
+
allow.append(normalized)
|
|
418
|
+
existing.add(normalized)
|
|
419
|
+
changed = True
|
|
420
|
+
|
|
421
|
+
return current, changed
|
|
422
|
+
|
|
423
|
+
|
|
147
424
|
def write_json_file(path: Path, payload: dict[str, Any]) -> Path:
|
|
148
425
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
149
426
|
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
@@ -201,6 +478,17 @@ def settings_have_hook_command(settings: dict[str, Any], command_text: str) -> b
|
|
|
201
478
|
return False
|
|
202
479
|
|
|
203
480
|
|
|
481
|
+
def settings_have_allowed_tools(settings: dict[str, Any], allow_rules: list[str]) -> bool:
|
|
482
|
+
permissions = settings.get("permissions")
|
|
483
|
+
if not isinstance(permissions, dict):
|
|
484
|
+
return False
|
|
485
|
+
allow = permissions.get("allow")
|
|
486
|
+
if not isinstance(allow, list):
|
|
487
|
+
return False
|
|
488
|
+
existing = {str(item).strip() for item in allow if str(item).strip()}
|
|
489
|
+
return all(str(rule).strip() in existing for rule in allow_rules)
|
|
490
|
+
|
|
491
|
+
|
|
204
492
|
def build_workspace_hook_command(workspace_paths: dict[str, Path]) -> str:
|
|
205
493
|
return "powershell -ExecutionPolicy Bypass -File .claude/hooks/route-edit.ps1"
|
|
206
494
|
|
|
@@ -235,11 +523,15 @@ def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
|
|
|
235
523
|
settings,
|
|
236
524
|
build_workspace_hook_command(workspace_paths),
|
|
237
525
|
)
|
|
526
|
+
merged_settings, permissions_changed = merge_permission_settings(merged_settings, DEFAULT_ALLOWED_TOOLS)
|
|
238
527
|
if settings_changed or not workspace_paths["settings"].exists():
|
|
239
528
|
write_json_file(workspace_paths["settings"], merged_settings)
|
|
529
|
+
elif permissions_changed:
|
|
530
|
+
write_json_file(workspace_paths["settings"], merged_settings)
|
|
240
531
|
|
|
241
532
|
if PROJECT_HOOK_PATH.resolve() != workspace_paths["hook_script"].resolve():
|
|
242
533
|
shutil.copyfile(PROJECT_HOOK_PATH, workspace_paths["hook_script"])
|
|
534
|
+
written_commands = write_custom_command_files(workspace_paths["commands_dir"], WORKSPACE / "bin")
|
|
243
535
|
|
|
244
536
|
summary = build_mode_summary_payload(
|
|
245
537
|
scope="项目级 Claude 与 MCP 集成面",
|
|
@@ -255,10 +547,14 @@ def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
|
|
|
255
547
|
"routing_file": str(routing_path),
|
|
256
548
|
"claude_settings": str(workspace_paths["settings"]),
|
|
257
549
|
"hook_script": str(workspace_paths["hook_script"]),
|
|
550
|
+
"commands_dir": str(workspace_paths["commands_dir"]),
|
|
551
|
+
"command_files": written_commands,
|
|
258
552
|
"notes": [
|
|
259
553
|
"Repository-local MCP config was synced from the executor registry.",
|
|
260
554
|
"Project-local model-routing.yaml was synchronized and preserves custom path blocks.",
|
|
261
555
|
"Claude pre-edit guardrail hook was synchronized into the target workspace.",
|
|
556
|
+
"Claude MCP tool permissions were merged into project .claude/settings.json.",
|
|
557
|
+
"Project-local Claude slash commands were synchronized into .claude/commands.",
|
|
262
558
|
"This mode prepares project-level integration surfaces for the selected workspace.",
|
|
263
559
|
],
|
|
264
560
|
"summary": summary,
|
|
@@ -273,6 +569,7 @@ def preview_user_install(override_root: str = "") -> dict[str, Any]:
|
|
|
273
569
|
user_paths = get_user_claude_paths(override_root)
|
|
274
570
|
user_settings = load_json_file(user_paths["settings"])
|
|
275
571
|
merged_settings, settings_changed = merge_hook_settings(user_settings, build_user_hook_command(user_paths))
|
|
572
|
+
merged_settings, permissions_changed = merge_permission_settings(merged_settings, DEFAULT_ALLOWED_TOOLS)
|
|
276
573
|
mcp_config = build_mcp_config()
|
|
277
574
|
recommended_next_action = f"cgc-install --mode user --user-root {shell_quote(str(user_paths['root']))}"
|
|
278
575
|
summary = build_mode_summary_payload(
|
|
@@ -290,11 +587,13 @@ def preview_user_install(override_root: str = "") -> dict[str, Any]:
|
|
|
290
587
|
"settings_json": str(user_paths["settings"]),
|
|
291
588
|
"mcp_json": str(user_paths["mcp"]),
|
|
292
589
|
"hook_script": str(user_paths["hook_script"]),
|
|
590
|
+
"commands_dir": str(user_paths["commands_dir"]),
|
|
293
591
|
},
|
|
294
592
|
"would_write": {
|
|
295
|
-
"settings_changed": settings_changed or not user_paths["settings"].exists(),
|
|
593
|
+
"settings_changed": settings_changed or permissions_changed or not user_paths["settings"].exists(),
|
|
296
594
|
"mcp_changed": True,
|
|
297
595
|
"hook_changed": True,
|
|
596
|
+
"commands_changed": True,
|
|
298
597
|
},
|
|
299
598
|
"preview": {
|
|
300
599
|
"settings": sanitize_for_preview(merged_settings),
|
|
@@ -303,6 +602,7 @@ def preview_user_install(override_root: str = "") -> dict[str, Any]:
|
|
|
303
602
|
"notes": [
|
|
304
603
|
"This mode does not modify user-level Claude files.",
|
|
305
604
|
"Use this preview to inspect the future user-level integration surface.",
|
|
605
|
+
"The preview includes MCP tool allow rules for codecgc, codex, and gemini servers.",
|
|
306
606
|
"Current CodeCGC product policy still defaults to project-local installation.",
|
|
307
607
|
],
|
|
308
608
|
"summary": summary,
|
|
@@ -313,12 +613,15 @@ def install_user_runtime(override_root: str = "") -> dict[str, Any]:
|
|
|
313
613
|
user_paths = get_user_claude_paths(override_root)
|
|
314
614
|
user_paths["root"].mkdir(parents=True, exist_ok=True)
|
|
315
615
|
user_paths["hooks_dir"].mkdir(parents=True, exist_ok=True)
|
|
616
|
+
user_paths["commands_dir"].mkdir(parents=True, exist_ok=True)
|
|
316
617
|
|
|
317
618
|
settings = load_json_file(user_paths["settings"])
|
|
318
619
|
merged_settings, settings_changed = merge_hook_settings(settings, build_user_hook_command(user_paths))
|
|
620
|
+
merged_settings, permissions_changed = merge_permission_settings(merged_settings, DEFAULT_ALLOWED_TOOLS)
|
|
319
621
|
write_json_file(user_paths["settings"], merged_settings)
|
|
320
622
|
write_json_file(user_paths["mcp"], build_mcp_config())
|
|
321
623
|
shutil.copyfile(PROJECT_HOOK_PATH, user_paths["hook_script"])
|
|
624
|
+
written_commands = write_custom_command_files(user_paths["commands_dir"], WORKSPACE / "bin")
|
|
322
625
|
summary = build_mode_summary_payload(
|
|
323
626
|
scope="用户级 Claude 集成面",
|
|
324
627
|
human_summary="用户级 Claude 集成文件已写入。",
|
|
@@ -334,15 +637,20 @@ def install_user_runtime(override_root: str = "") -> dict[str, Any]:
|
|
|
334
637
|
"settings_json": str(user_paths["settings"]),
|
|
335
638
|
"mcp_json": str(user_paths["mcp"]),
|
|
336
639
|
"hook_script": str(user_paths["hook_script"]),
|
|
640
|
+
"commands_dir": str(user_paths["commands_dir"]),
|
|
337
641
|
},
|
|
338
642
|
"changes": {
|
|
339
|
-
"settings_changed": settings_changed or not user_paths["settings"].exists(),
|
|
643
|
+
"settings_changed": settings_changed or permissions_changed or not user_paths["settings"].exists(),
|
|
340
644
|
"mcp_changed": True,
|
|
341
645
|
"hook_changed": True,
|
|
646
|
+
"commands_changed": True,
|
|
342
647
|
},
|
|
648
|
+
"command_files": written_commands,
|
|
343
649
|
"notes": [
|
|
344
650
|
"User-level Claude integration files were written to the selected root.",
|
|
345
651
|
"The user-level hook script was copied from the project hook source.",
|
|
652
|
+
"MCP tool allow rules were merged into ~/.claude/settings.json.",
|
|
653
|
+
"User-level Claude slash commands were written to ~/.claude/commands.",
|
|
346
654
|
"This mode is explicit and should be used only when a broader Claude integration surface is intended.",
|
|
347
655
|
],
|
|
348
656
|
"summary": summary,
|
|
@@ -373,6 +681,7 @@ def build_install_mode_summary(result: dict[str, Any]) -> str:
|
|
|
373
681
|
f"- Routing 文件: {result.get('routing_file', '')}",
|
|
374
682
|
f"- Claude 设置: {result.get('claude_settings', '')}",
|
|
375
683
|
f"- Hook 脚本: {result.get('hook_script', '')}",
|
|
684
|
+
f"- Slash Commands: {result.get('commands_dir', '')}",
|
|
376
685
|
"- 说明: 可选外部能力如 MemOS 不由 cgc-install 自动写入;如需启用,请在 Claude 中单独配置官方 MCP。",
|
|
377
686
|
]
|
|
378
687
|
next_actions = [
|
|
@@ -391,6 +700,7 @@ def build_install_mode_summary(result: dict[str, Any]) -> str:
|
|
|
391
700
|
f"- 预演 Settings: {planned.get('settings_json', '')}",
|
|
392
701
|
f"- 预演 MCP: {planned.get('mcp_json', '')}",
|
|
393
702
|
f"- 预演 Hook: {planned.get('hook_script', '')}",
|
|
703
|
+
f"- 预演 Slash Commands: {planned.get('commands_dir', '')}",
|
|
394
704
|
"- 说明: 该预演只覆盖 CodeCGC 必需执行器;MemOS 等可选外部能力仍建议在 Claude 中独立配置。",
|
|
395
705
|
]
|
|
396
706
|
next_actions = []
|
|
@@ -410,6 +720,7 @@ def build_install_mode_summary(result: dict[str, Any]) -> str:
|
|
|
410
720
|
f"- Settings: {written.get('settings_json', '')}",
|
|
411
721
|
f"- MCP: {written.get('mcp_json', '')}",
|
|
412
722
|
f"- Hook 脚本: {written.get('hook_script', '')}",
|
|
723
|
+
f"- Slash Commands: {written.get('commands_dir', '')}",
|
|
413
724
|
"- 说明: 该安装只写入 CodeCGC 必需执行器;MemOS 等可选外部能力仍需在 Claude 中单独配置。",
|
|
414
725
|
]
|
|
415
726
|
next_actions = [
|
|
@@ -431,6 +742,7 @@ def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
431
742
|
routing_exists = workspace_paths["routing_file"].exists()
|
|
432
743
|
|
|
433
744
|
hook_registered = settings_have_hook_command(current_settings, expected_hook_command)
|
|
745
|
+
permissions_registered = settings_have_allowed_tools(current_settings, DEFAULT_ALLOWED_TOOLS)
|
|
434
746
|
mcp_matches = current_mcp == expected_mcp if workspace_paths["mcp"].exists() else False
|
|
435
747
|
hook_file_matches = current_hook_text == expected_hook_text if workspace_paths["hook_script"].exists() else False
|
|
436
748
|
|
|
@@ -441,6 +753,8 @@ def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
441
753
|
missing.append("mcp_json")
|
|
442
754
|
if not hook_registered:
|
|
443
755
|
missing.append("claude_settings_hook")
|
|
756
|
+
if not permissions_registered:
|
|
757
|
+
missing.append("claude_settings_permissions")
|
|
444
758
|
if not hook_file_matches:
|
|
445
759
|
missing.append("hook_script")
|
|
446
760
|
|
|
@@ -456,11 +770,13 @@ def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
456
770
|
"hook_exists": workspace_paths["hook_script"].exists(),
|
|
457
771
|
"mcp_matches_expected": mcp_matches,
|
|
458
772
|
"hook_registered": hook_registered,
|
|
773
|
+
"permissions_registered": permissions_registered,
|
|
459
774
|
"hook_file_matches_expected": hook_file_matches,
|
|
460
775
|
"ready": ready,
|
|
461
776
|
"missing_or_outdated": missing,
|
|
462
777
|
"recommended_command": "" if ready else build_workspace_install_command(workspace_paths["root"]),
|
|
463
778
|
"hook_expected": {"hooks": build_hook_payload(expected_hook_command)},
|
|
779
|
+
"permissions_expected": {"permissions": {"allow": DEFAULT_ALLOWED_TOOLS}},
|
|
464
780
|
}
|
|
465
781
|
|
|
466
782
|
|
|
@@ -473,6 +789,7 @@ def collect_user_status(user_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
473
789
|
current_hook_text = load_text_file(user_paths["hook_script"])
|
|
474
790
|
|
|
475
791
|
hook_registered = settings_have_hook_command(current_settings, expected_hook_command)
|
|
792
|
+
permissions_registered = settings_have_allowed_tools(current_settings, DEFAULT_ALLOWED_TOOLS)
|
|
476
793
|
mcp_matches = current_mcp == expected_mcp if user_paths["mcp"].exists() else False
|
|
477
794
|
hook_file_matches = current_hook_text == expected_hook_text if user_paths["hook_script"].exists() else False
|
|
478
795
|
|
|
@@ -481,6 +798,8 @@ def collect_user_status(user_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
481
798
|
missing.append("mcp_json")
|
|
482
799
|
if not hook_registered:
|
|
483
800
|
missing.append("claude_settings_hook")
|
|
801
|
+
if not permissions_registered:
|
|
802
|
+
missing.append("claude_settings_permissions")
|
|
484
803
|
if not hook_file_matches:
|
|
485
804
|
missing.append("hook_script")
|
|
486
805
|
|
|
@@ -495,6 +814,7 @@ def collect_user_status(user_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
495
814
|
"hook_exists": user_paths["hook_script"].exists(),
|
|
496
815
|
"mcp_matches_expected": mcp_matches,
|
|
497
816
|
"hook_registered": hook_registered,
|
|
817
|
+
"permissions_registered": permissions_registered,
|
|
498
818
|
"hook_file_matches_expected": hook_file_matches,
|
|
499
819
|
"ready": ready,
|
|
500
820
|
"missing_or_outdated": missing,
|
|
@@ -583,14 +903,18 @@ def build_pip_install_command(python_command: str, requirement: str) -> str:
|
|
|
583
903
|
|
|
584
904
|
def build_local_editable_install_command(python_command: str) -> str:
|
|
585
905
|
runtime_command = python_command.strip() or sys.executable
|
|
906
|
+
codecgcmcp_path = WORKSPACE / "codecgcmcp"
|
|
586
907
|
codexmcp_path = WORKSPACE / "codexmcp"
|
|
587
908
|
geminimcp_path = WORKSPACE / "geminimcp"
|
|
909
|
+
if not (codecgcmcp_path / "pyproject.toml").exists():
|
|
910
|
+
return ""
|
|
588
911
|
if not (codexmcp_path / "pyproject.toml").exists():
|
|
589
912
|
return ""
|
|
590
913
|
if not (geminimcp_path / "pyproject.toml").exists():
|
|
591
914
|
return ""
|
|
592
915
|
return (
|
|
593
|
-
f"{shell_quote(runtime_command)} -m pip install -e {shell_quote(str(
|
|
916
|
+
f"{shell_quote(runtime_command)} -m pip install -e {shell_quote(str(codecgcmcp_path))} "
|
|
917
|
+
f"-e {shell_quote(str(codexmcp_path))} "
|
|
594
918
|
f"-e {shell_quote(str(geminimcp_path))}"
|
|
595
919
|
)
|
|
596
920
|
|
|
@@ -663,6 +987,25 @@ def classify_doctor_failures(
|
|
|
663
987
|
)
|
|
664
988
|
continue
|
|
665
989
|
|
|
990
|
+
if name == "python_runtime_import_probe_codecgcmcp":
|
|
991
|
+
if configured_python_missing:
|
|
992
|
+
continue
|
|
993
|
+
if "No module named 'codecgcmcp'" in detail or 'No module named "codecgcmcp"' in detail:
|
|
994
|
+
add_failure(
|
|
995
|
+
"codecgcmcp-package-missing",
|
|
996
|
+
"当前解释器无法导入本地 `codecgcmcp` 包。",
|
|
997
|
+
"确认当前安装包已包含 `codecgcmcp/src`;仓库开发环境可执行本地 editable install,安装产物则应重新安装 CodeCGC 包。",
|
|
998
|
+
editable_install_command,
|
|
999
|
+
)
|
|
1000
|
+
else:
|
|
1001
|
+
add_failure(
|
|
1002
|
+
"codecgcmcp-runtime-broken",
|
|
1003
|
+
"`codecgcmcp` 启动入口存在,但当前运行时仍无法导入。",
|
|
1004
|
+
"仓库开发环境可先重装本地编排器包;若你使用的是已安装产物,则优先重新安装 CodeCGC,再检查编排器源码是否缺失或损坏。",
|
|
1005
|
+
editable_install_command,
|
|
1006
|
+
)
|
|
1007
|
+
continue
|
|
1008
|
+
|
|
666
1009
|
if name == "python_runtime_import_probe_codexmcp":
|
|
667
1010
|
if configured_python_missing:
|
|
668
1011
|
continue
|
|
@@ -776,11 +1119,18 @@ def collect_doctor_status(override_workspace: str = "") -> dict[str, Any]:
|
|
|
776
1119
|
|
|
777
1120
|
runtime_probe_command = configured_python_command if configured_python_command else (python_command or sys.executable)
|
|
778
1121
|
runtime_env = dict(os.environ)
|
|
779
|
-
combined_pythonpath = os.pathsep.join(
|
|
1122
|
+
combined_pythonpath = os.pathsep.join(
|
|
1123
|
+
[
|
|
1124
|
+
str(WORKSPACE / "scripts"),
|
|
1125
|
+
str(WORKSPACE / "codecgcmcp" / "src"),
|
|
1126
|
+
str(WORKSPACE / "codexmcp" / "src"),
|
|
1127
|
+
str(WORKSPACE / "geminimcp" / "src"),
|
|
1128
|
+
]
|
|
1129
|
+
)
|
|
780
1130
|
existing_pythonpath = runtime_env.get("PYTHONPATH", "").strip()
|
|
781
1131
|
runtime_env["PYTHONPATH"] = f"{combined_pythonpath}{os.pathsep}{existing_pythonpath}" if existing_pythonpath else combined_pythonpath
|
|
782
1132
|
|
|
783
|
-
for module_name in ("mcp", "codexmcp.cli", "geminimcp.cli"):
|
|
1133
|
+
for module_name in ("mcp", "codecgcmcp.cli", "codexmcp.cli", "geminimcp.cli"):
|
|
784
1134
|
probe = probe_python_import(runtime_probe_command, module_name, runtime_env)
|
|
785
1135
|
checks.append(
|
|
786
1136
|
{
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import json
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
|
|
@@ -8,16 +9,32 @@ WORKSPACE = Path(__file__).resolve().parents[1]
|
|
|
8
9
|
MCP_CONFIG_PATH = WORKSPACE / ".mcp.json"
|
|
9
10
|
|
|
10
11
|
|
|
12
|
+
def build_runtime_pythonpath(*extra_paths: Path) -> str:
|
|
13
|
+
paths = [str(WORKSPACE / "scripts"), *(str(path) for path in extra_paths)]
|
|
14
|
+
return os.pathsep.join(paths)
|
|
15
|
+
|
|
16
|
+
|
|
11
17
|
def build_mcp_config() -> dict:
|
|
12
18
|
registry = build_executor_registry()
|
|
13
19
|
servers: dict[str, dict] = {}
|
|
14
20
|
|
|
21
|
+
servers["codecgc"] = {
|
|
22
|
+
"command": str(next(iter(registry.values()))["python_command"]),
|
|
23
|
+
"args": ["-m", "codecgcmcp.cli"],
|
|
24
|
+
"env": {
|
|
25
|
+
"PYTHONPATH": build_runtime_pythonpath(WORKSPACE / "codecgcmcp" / "src"),
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
15
29
|
for config in registry.values():
|
|
16
30
|
servers[str(config["mcp_server_name"])] = {
|
|
17
31
|
"command": str(config["python_command"]),
|
|
18
32
|
"args": ["-m", str(config["python_module"])],
|
|
19
33
|
"env": {
|
|
20
|
-
"PYTHONPATH":
|
|
34
|
+
"PYTHONPATH": build_runtime_pythonpath(
|
|
35
|
+
WORKSPACE / "codecgcmcp" / "src",
|
|
36
|
+
Path(str(config["pythonpath"])),
|
|
37
|
+
),
|
|
21
38
|
},
|
|
22
39
|
}
|
|
23
40
|
|