@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 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
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.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,12 @@
1
+ """Console entry point for the CodeCGC MCP server."""
2
+
3
+ from codecgcmcp.server import run
4
+
5
+
6
+ def main() -> None:
7
+ """Start the CodeCGC MCP server."""
8
+ run()
9
+
10
+
11
+ if __name__ == "__main__":
12
+ main()
@@ -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")
@@ -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.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",
@@ -28,6 +28,7 @@ def run_json_script(script_name: str, *args: str) -> dict[str, Any]:
28
28
  completed = subprocess.run(
29
29
  command,
30
30
  cwd=PROJECT_WORKSPACE,
31
+ stdin=subprocess.DEVNULL,
31
32
  capture_output=True,
32
33
  text=True,
33
34
  encoding="utf-8",
@@ -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(codexmcp_path))} "
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([str(WORKSPACE / "codexmcp" / "src"), str(WORKSPACE / "geminimcp" / "src")])
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": str(config["pythonpath"]),
34
+ "PYTHONPATH": build_runtime_pythonpath(
35
+ WORKSPACE / "codecgcmcp" / "src",
36
+ Path(str(config["pythonpath"])),
37
+ ),
21
38
  },
22
39
  }
23
40