@hunyed15/codecgc 0.1.2 → 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/README.md +22 -0
- 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 +320 -60
- package/scripts/sync_codecgc_mcp_config.py +18 -1
package/README.md
CHANGED
|
@@ -52,12 +52,34 @@ cgc-install --mode user
|
|
|
52
52
|
- `~/.claude/hooks/route-edit.ps1`
|
|
53
53
|
- `~/.claude/commands/cgc*.md` 自定义 slash commands
|
|
54
54
|
|
|
55
|
+
当前安装链路还会注册 3 个 MCP server:
|
|
56
|
+
|
|
57
|
+
- `codecgc`:CodeCGC 编排器 MCP
|
|
58
|
+
- `codex`:后端执行器 MCP
|
|
59
|
+
- `gemini`:前端执行器 MCP
|
|
60
|
+
|
|
55
61
|
安装完成后,可以在 Claude 中直接使用:
|
|
56
62
|
|
|
57
63
|
- `/cgc`
|
|
58
64
|
- `/cgc-install`
|
|
59
65
|
- `/cgc-status`
|
|
60
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
|
|
61
83
|
|
|
62
84
|
如果安装时 Python 尚未就绪,自动集成会跳过,此时可在安装 Python 后手动执行 `cgc-install --mode user`。
|
|
63
85
|
|
|
@@ -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:
|
|
@@ -116,62 +121,232 @@ def _normalize_command_path_for_markdown(path: Path) -> str:
|
|
|
116
121
|
return str(path).replace("\\", "\\\\")
|
|
117
122
|
|
|
118
123
|
|
|
119
|
-
def
|
|
120
|
-
|
|
121
|
-
|
|
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"
|
|
124
153
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
""
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
""
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
175
350
|
|
|
176
351
|
|
|
177
352
|
def write_custom_command_files(target_dir: Path, bin_dir: Path) -> list[str]:
|
|
@@ -219,6 +394,33 @@ def merge_hook_settings(current: dict[str, Any], command_text: str) -> tuple[dic
|
|
|
219
394
|
return current, True
|
|
220
395
|
|
|
221
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
|
+
|
|
222
424
|
def write_json_file(path: Path, payload: dict[str, Any]) -> Path:
|
|
223
425
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
224
426
|
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
@@ -276,6 +478,17 @@ def settings_have_hook_command(settings: dict[str, Any], command_text: str) -> b
|
|
|
276
478
|
return False
|
|
277
479
|
|
|
278
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
|
+
|
|
279
492
|
def build_workspace_hook_command(workspace_paths: dict[str, Path]) -> str:
|
|
280
493
|
return "powershell -ExecutionPolicy Bypass -File .claude/hooks/route-edit.ps1"
|
|
281
494
|
|
|
@@ -310,8 +523,11 @@ def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
|
|
|
310
523
|
settings,
|
|
311
524
|
build_workspace_hook_command(workspace_paths),
|
|
312
525
|
)
|
|
526
|
+
merged_settings, permissions_changed = merge_permission_settings(merged_settings, DEFAULT_ALLOWED_TOOLS)
|
|
313
527
|
if settings_changed or not workspace_paths["settings"].exists():
|
|
314
528
|
write_json_file(workspace_paths["settings"], merged_settings)
|
|
529
|
+
elif permissions_changed:
|
|
530
|
+
write_json_file(workspace_paths["settings"], merged_settings)
|
|
315
531
|
|
|
316
532
|
if PROJECT_HOOK_PATH.resolve() != workspace_paths["hook_script"].resolve():
|
|
317
533
|
shutil.copyfile(PROJECT_HOOK_PATH, workspace_paths["hook_script"])
|
|
@@ -337,6 +553,7 @@ def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
|
|
|
337
553
|
"Repository-local MCP config was synced from the executor registry.",
|
|
338
554
|
"Project-local model-routing.yaml was synchronized and preserves custom path blocks.",
|
|
339
555
|
"Claude pre-edit guardrail hook was synchronized into the target workspace.",
|
|
556
|
+
"Claude MCP tool permissions were merged into project .claude/settings.json.",
|
|
340
557
|
"Project-local Claude slash commands were synchronized into .claude/commands.",
|
|
341
558
|
"This mode prepares project-level integration surfaces for the selected workspace.",
|
|
342
559
|
],
|
|
@@ -352,6 +569,7 @@ def preview_user_install(override_root: str = "") -> dict[str, Any]:
|
|
|
352
569
|
user_paths = get_user_claude_paths(override_root)
|
|
353
570
|
user_settings = load_json_file(user_paths["settings"])
|
|
354
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)
|
|
355
573
|
mcp_config = build_mcp_config()
|
|
356
574
|
recommended_next_action = f"cgc-install --mode user --user-root {shell_quote(str(user_paths['root']))}"
|
|
357
575
|
summary = build_mode_summary_payload(
|
|
@@ -372,7 +590,7 @@ def preview_user_install(override_root: str = "") -> dict[str, Any]:
|
|
|
372
590
|
"commands_dir": str(user_paths["commands_dir"]),
|
|
373
591
|
},
|
|
374
592
|
"would_write": {
|
|
375
|
-
"settings_changed": settings_changed or not user_paths["settings"].exists(),
|
|
593
|
+
"settings_changed": settings_changed or permissions_changed or not user_paths["settings"].exists(),
|
|
376
594
|
"mcp_changed": True,
|
|
377
595
|
"hook_changed": True,
|
|
378
596
|
"commands_changed": True,
|
|
@@ -384,6 +602,7 @@ def preview_user_install(override_root: str = "") -> dict[str, Any]:
|
|
|
384
602
|
"notes": [
|
|
385
603
|
"This mode does not modify user-level Claude files.",
|
|
386
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.",
|
|
387
606
|
"Current CodeCGC product policy still defaults to project-local installation.",
|
|
388
607
|
],
|
|
389
608
|
"summary": summary,
|
|
@@ -398,6 +617,7 @@ def install_user_runtime(override_root: str = "") -> dict[str, Any]:
|
|
|
398
617
|
|
|
399
618
|
settings = load_json_file(user_paths["settings"])
|
|
400
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)
|
|
401
621
|
write_json_file(user_paths["settings"], merged_settings)
|
|
402
622
|
write_json_file(user_paths["mcp"], build_mcp_config())
|
|
403
623
|
shutil.copyfile(PROJECT_HOOK_PATH, user_paths["hook_script"])
|
|
@@ -420,7 +640,7 @@ def install_user_runtime(override_root: str = "") -> dict[str, Any]:
|
|
|
420
640
|
"commands_dir": str(user_paths["commands_dir"]),
|
|
421
641
|
},
|
|
422
642
|
"changes": {
|
|
423
|
-
"settings_changed": settings_changed or not user_paths["settings"].exists(),
|
|
643
|
+
"settings_changed": settings_changed or permissions_changed or not user_paths["settings"].exists(),
|
|
424
644
|
"mcp_changed": True,
|
|
425
645
|
"hook_changed": True,
|
|
426
646
|
"commands_changed": True,
|
|
@@ -429,6 +649,7 @@ def install_user_runtime(override_root: str = "") -> dict[str, Any]:
|
|
|
429
649
|
"notes": [
|
|
430
650
|
"User-level Claude integration files were written to the selected root.",
|
|
431
651
|
"The user-level hook script was copied from the project hook source.",
|
|
652
|
+
"MCP tool allow rules were merged into ~/.claude/settings.json.",
|
|
432
653
|
"User-level Claude slash commands were written to ~/.claude/commands.",
|
|
433
654
|
"This mode is explicit and should be used only when a broader Claude integration surface is intended.",
|
|
434
655
|
],
|
|
@@ -521,6 +742,7 @@ def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
521
742
|
routing_exists = workspace_paths["routing_file"].exists()
|
|
522
743
|
|
|
523
744
|
hook_registered = settings_have_hook_command(current_settings, expected_hook_command)
|
|
745
|
+
permissions_registered = settings_have_allowed_tools(current_settings, DEFAULT_ALLOWED_TOOLS)
|
|
524
746
|
mcp_matches = current_mcp == expected_mcp if workspace_paths["mcp"].exists() else False
|
|
525
747
|
hook_file_matches = current_hook_text == expected_hook_text if workspace_paths["hook_script"].exists() else False
|
|
526
748
|
|
|
@@ -531,6 +753,8 @@ def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
531
753
|
missing.append("mcp_json")
|
|
532
754
|
if not hook_registered:
|
|
533
755
|
missing.append("claude_settings_hook")
|
|
756
|
+
if not permissions_registered:
|
|
757
|
+
missing.append("claude_settings_permissions")
|
|
534
758
|
if not hook_file_matches:
|
|
535
759
|
missing.append("hook_script")
|
|
536
760
|
|
|
@@ -546,11 +770,13 @@ def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
546
770
|
"hook_exists": workspace_paths["hook_script"].exists(),
|
|
547
771
|
"mcp_matches_expected": mcp_matches,
|
|
548
772
|
"hook_registered": hook_registered,
|
|
773
|
+
"permissions_registered": permissions_registered,
|
|
549
774
|
"hook_file_matches_expected": hook_file_matches,
|
|
550
775
|
"ready": ready,
|
|
551
776
|
"missing_or_outdated": missing,
|
|
552
777
|
"recommended_command": "" if ready else build_workspace_install_command(workspace_paths["root"]),
|
|
553
778
|
"hook_expected": {"hooks": build_hook_payload(expected_hook_command)},
|
|
779
|
+
"permissions_expected": {"permissions": {"allow": DEFAULT_ALLOWED_TOOLS}},
|
|
554
780
|
}
|
|
555
781
|
|
|
556
782
|
|
|
@@ -563,6 +789,7 @@ def collect_user_status(user_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
563
789
|
current_hook_text = load_text_file(user_paths["hook_script"])
|
|
564
790
|
|
|
565
791
|
hook_registered = settings_have_hook_command(current_settings, expected_hook_command)
|
|
792
|
+
permissions_registered = settings_have_allowed_tools(current_settings, DEFAULT_ALLOWED_TOOLS)
|
|
566
793
|
mcp_matches = current_mcp == expected_mcp if user_paths["mcp"].exists() else False
|
|
567
794
|
hook_file_matches = current_hook_text == expected_hook_text if user_paths["hook_script"].exists() else False
|
|
568
795
|
|
|
@@ -571,6 +798,8 @@ def collect_user_status(user_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
571
798
|
missing.append("mcp_json")
|
|
572
799
|
if not hook_registered:
|
|
573
800
|
missing.append("claude_settings_hook")
|
|
801
|
+
if not permissions_registered:
|
|
802
|
+
missing.append("claude_settings_permissions")
|
|
574
803
|
if not hook_file_matches:
|
|
575
804
|
missing.append("hook_script")
|
|
576
805
|
|
|
@@ -585,6 +814,7 @@ def collect_user_status(user_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
585
814
|
"hook_exists": user_paths["hook_script"].exists(),
|
|
586
815
|
"mcp_matches_expected": mcp_matches,
|
|
587
816
|
"hook_registered": hook_registered,
|
|
817
|
+
"permissions_registered": permissions_registered,
|
|
588
818
|
"hook_file_matches_expected": hook_file_matches,
|
|
589
819
|
"ready": ready,
|
|
590
820
|
"missing_or_outdated": missing,
|
|
@@ -673,14 +903,18 @@ def build_pip_install_command(python_command: str, requirement: str) -> str:
|
|
|
673
903
|
|
|
674
904
|
def build_local_editable_install_command(python_command: str) -> str:
|
|
675
905
|
runtime_command = python_command.strip() or sys.executable
|
|
906
|
+
codecgcmcp_path = WORKSPACE / "codecgcmcp"
|
|
676
907
|
codexmcp_path = WORKSPACE / "codexmcp"
|
|
677
908
|
geminimcp_path = WORKSPACE / "geminimcp"
|
|
909
|
+
if not (codecgcmcp_path / "pyproject.toml").exists():
|
|
910
|
+
return ""
|
|
678
911
|
if not (codexmcp_path / "pyproject.toml").exists():
|
|
679
912
|
return ""
|
|
680
913
|
if not (geminimcp_path / "pyproject.toml").exists():
|
|
681
914
|
return ""
|
|
682
915
|
return (
|
|
683
|
-
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))} "
|
|
684
918
|
f"-e {shell_quote(str(geminimcp_path))}"
|
|
685
919
|
)
|
|
686
920
|
|
|
@@ -753,6 +987,25 @@ def classify_doctor_failures(
|
|
|
753
987
|
)
|
|
754
988
|
continue
|
|
755
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
|
+
|
|
756
1009
|
if name == "python_runtime_import_probe_codexmcp":
|
|
757
1010
|
if configured_python_missing:
|
|
758
1011
|
continue
|
|
@@ -866,11 +1119,18 @@ def collect_doctor_status(override_workspace: str = "") -> dict[str, Any]:
|
|
|
866
1119
|
|
|
867
1120
|
runtime_probe_command = configured_python_command if configured_python_command else (python_command or sys.executable)
|
|
868
1121
|
runtime_env = dict(os.environ)
|
|
869
|
-
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
|
+
)
|
|
870
1130
|
existing_pythonpath = runtime_env.get("PYTHONPATH", "").strip()
|
|
871
1131
|
runtime_env["PYTHONPATH"] = f"{combined_pythonpath}{os.pathsep}{existing_pythonpath}" if existing_pythonpath else combined_pythonpath
|
|
872
1132
|
|
|
873
|
-
for module_name in ("mcp", "codexmcp.cli", "geminimcp.cli"):
|
|
1133
|
+
for module_name in ("mcp", "codecgcmcp.cli", "codexmcp.cli", "geminimcp.cli"):
|
|
874
1134
|
probe = probe_python_import(runtime_probe_command, module_name, runtime_env)
|
|
875
1135
|
checks.append(
|
|
876
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
|
|