@hunyed15/codecgc 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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,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.2",
3
+ "version": "0.1.4",
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:
@@ -116,62 +121,233 @@ def _normalize_command_path_for_markdown(path: Path) -> str:
116
121
  return str(path).replace("\\", "\\\\")
117
122
 
118
123
 
119
- def build_custom_command_templates(bin_dir: Path) -> dict[str, str]:
120
- cgc_js = _normalize_command_path_for_markdown(bin_dir / "cgc.js")
121
- install_js = _normalize_command_path_for_markdown(bin_dir / "cgc-install.js")
122
- status_js = _normalize_command_path_for_markdown(bin_dir / "cgc-status.js")
123
- doctor_js = _normalize_command_path_for_markdown(bin_dir / "cgc-doctor.js")
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"优先使用 `{primary_tool}` MCP 工具作为主执行路径。",
140
+ "内部思考语言可自行选择,但面向用户的最终回复默认使用中文。",
141
+ "",
142
+ "执行规则:",
143
+ ]
144
+ lines.extend(f"- {item}" for item in direct_rules)
145
+ if missing_rules:
146
+ lines.append("")
147
+ lines.append("缺少参数时:")
148
+ lines.extend(f"- {item}" for item in missing_rules)
149
+ lines.append("")
150
+ lines.append("回退规则:")
151
+ lines.append(f"- 只有在 MCP 工具路径不可用,或用户明确要求走 CLI 时,才回退到 Bash + `{fallback_command}`。")
152
+ lines.append("- 向用户用中文简要总结结果。")
153
+ return filename, "\n".join(lines) + "\n"
124
154
 
125
- return {
126
- "cgc.md": f"""---
127
- description: Run CodeCGC in the current project
128
- argument-hint: "[request or flags]"
129
- ---
130
- Use the Bash tool to run CodeCGC in the current project directory.
131
-
132
- - If the user supplied arguments, run:
133
- `node "{cgc_js}" $ARGUMENTS`
134
- - If the user did not supply arguments, run:
135
- `node "{cgc_js}" --help`
136
- - Show the command you ran and summarize the result briefly.
137
- """,
138
- "cgc-install.md": f"""---
139
- description: Install or sync CodeCGC integration for the current project or user Claude profile
140
- argument-hint: "[flags]"
141
- ---
142
- Use the Bash tool to run the CodeCGC install command.
143
-
144
- - If the user supplied arguments, run:
145
- `node "{install_js}" $ARGUMENTS`
146
- - If the user did not supply arguments, run:
147
- `node "{install_js}"`
148
- - Show the command you ran and summarize the result briefly.
149
- """,
150
- "cgc-status.md": f"""---
151
- description: Check CodeCGC integration status
152
- argument-hint: "[flags]"
153
- ---
154
- Use the Bash tool to run the CodeCGC status command.
155
-
156
- - If the user supplied arguments, run:
157
- `node "{status_js}" $ARGUMENTS`
158
- - If the user did not supply arguments, run:
159
- `node "{status_js}"`
160
- - Show the command you ran and summarize the result briefly.
161
- """,
162
- "cgc-doctor.md": f"""---
163
- description: Run CodeCGC doctor checks
164
- argument-hint: "[flags]"
165
- ---
166
- Use the Bash tool to run the CodeCGC doctor command.
167
-
168
- - If the user supplied arguments, run:
169
- `node "{doctor_js}" $ARGUMENTS`
170
- - If the user did not supply arguments, run:
171
- `node "{doctor_js}"`
172
- - Show the command you ran and summarize the result briefly.
173
- """,
174
- }
155
+
156
+ def build_custom_command_templates(bin_dir: Path) -> dict[str, str]:
157
+ templates = dict(
158
+ [
159
+ build_mcp_first_command_template(
160
+ filename="cgc.md",
161
+ description="在当前项目中运行 CodeCGC",
162
+ argument_hint="[需求或参数]",
163
+ primary_tool="codecgc.entry",
164
+ direct_rules=[
165
+ "如果用户提供的是自然语言需求,传给 `codecgc.entry`。",
166
+ "如果用户想继续最近的工作,使用 `codecgc.continue`。",
167
+ "如果用户想知道下一步做什么,使用 `codecgc.explain`。",
168
+ ],
169
+ fallback_command="cgc",
170
+ ),
171
+ build_mcp_first_command_template(
172
+ filename="cgc-install.md",
173
+ description="为当前项目或 Claude 用户目录安装/同步 CodeCGC 集成",
174
+ argument_hint="[参数]",
175
+ primary_tool="codecgc.install",
176
+ direct_rules=[
177
+ "把安装参数映射到 `codecgc.install` 的 `mode`、`workspace`、`user_root` 等字段。",
178
+ "如果用户没有提供参数,就使用默认安装模式。",
179
+ ],
180
+ fallback_command="cgc-install",
181
+ ),
182
+ build_mcp_first_command_template(
183
+ filename="cgc-status.md",
184
+ description="检查 CodeCGC 集成状态",
185
+ argument_hint="[参数]",
186
+ primary_tool="codecgc.status",
187
+ direct_rules=[
188
+ "使用 `codecgc.status` 检查安装与集成就绪状态。",
189
+ "如果用户明确给出目标项目目录,映射 `workspace`。",
190
+ ],
191
+ fallback_command="cgc-status",
192
+ ),
193
+ build_mcp_first_command_template(
194
+ filename="cgc-doctor.md",
195
+ description="运行 CodeCGC 自检",
196
+ argument_hint="[参数]",
197
+ primary_tool="codecgc.doctor",
198
+ direct_rules=[
199
+ "使用 `codecgc.doctor` 检查运行时与集成健康状态。",
200
+ "如果用户明确给出目标项目目录,映射 `workspace`。",
201
+ ],
202
+ fallback_command="cgc-doctor",
203
+ ),
204
+ build_mcp_first_command_template(
205
+ filename="cgc-plan.md",
206
+ description="规划或修复一个 CodeCGC 工作流",
207
+ argument_hint="[结构化规划参数]",
208
+ primary_tool="codecgc.plan",
209
+ direct_rules=[
210
+ "调用前提取 `flow`、`slug` 和 `summary`。",
211
+ "映射用户提供的 `target_paths`、`kind`,以及 `goal`、`acceptance`、`risk` 等规划字段和 issue 专属字段。",
212
+ ],
213
+ missing_rules=[
214
+ "如果缺少 `flow`,询问这是 `feature` 还是 `issue` 工作流。",
215
+ "如果缺少 `slug`,询问稳定的工作流 slug。",
216
+ "如果缺少 `summary`,询问一个简短规划摘要。",
217
+ ],
218
+ fallback_command="cgc-plan",
219
+ ),
220
+ build_mcp_first_command_template(
221
+ filename="cgc-build.md",
222
+ description="执行 CodeCGC 功能开发步骤",
223
+ argument_hint="[参数]",
224
+ primary_tool="codecgc.build",
225
+ direct_rules=[
226
+ "调用前提取 `slug`。",
227
+ "映射可选执行字段,如 `step_number`、`checklist_file`、`audit_root`、`timeout_seconds`、`session_id`、`dry_run`。",
228
+ ],
229
+ missing_rules=[
230
+ "如果缺少 `slug`,询问目标功能工作流的 slug。",
231
+ ],
232
+ fallback_command="cgc-build",
233
+ ),
234
+ build_mcp_first_command_template(
235
+ filename="cgc-fix.md",
236
+ description="执行 CodeCGC 问题修复步骤",
237
+ argument_hint="[参数]",
238
+ primary_tool="codecgc.fix",
239
+ direct_rules=[
240
+ "调用前提取 `slug`。",
241
+ "映射可选执行字段,如 `step_number`、`checklist_file`、`audit_root`、`timeout_seconds`、`session_id`、`dry_run`。",
242
+ ],
243
+ missing_rules=[
244
+ "如果缺少 `slug`,询问目标问题工作流的 slug。",
245
+ ],
246
+ fallback_command="cgc-fix",
247
+ ),
248
+ build_mcp_first_command_template(
249
+ filename="cgc-test.md",
250
+ description="执行 CodeCGC 测试步骤",
251
+ argument_hint="[参数]",
252
+ primary_tool="codecgc.test",
253
+ direct_rules=[
254
+ "调用前提取 `flow` 和 `slug`。",
255
+ "映射可选执行字段,如 `step_number`、`checklist_file`、`audit_root`、`timeout_seconds`、`session_id`、`dry_run`。",
256
+ ],
257
+ missing_rules=[
258
+ "如果缺少 `flow`,询问该测试属于 `feature` 还是 `issue` 工作流。",
259
+ "如果缺少 `slug`,询问目标工作流 slug。",
260
+ ],
261
+ fallback_command="cgc-test",
262
+ ),
263
+ build_mcp_first_command_template(
264
+ filename="cgc-review.md",
265
+ description="审核一份 CodeCGC 执行审计结果",
266
+ argument_hint="[参数]",
267
+ primary_tool="codecgc.review",
268
+ direct_rules=[
269
+ "调用前提取 `audit_file` 和 `decision`。",
270
+ "如果用户明确提供,映射可选字段 `risk`、`next_step`、`force`。",
271
+ ],
272
+ missing_rules=[
273
+ "如果缺少 `audit_file`,询问审计 JSON 路径。",
274
+ "如果缺少 `decision`,询问审核结论是 `accepted` 还是 `changes-requested`。",
275
+ ],
276
+ fallback_command="cgc-review",
277
+ ),
278
+ build_mcp_first_command_template(
279
+ filename="cgc-route.md",
280
+ description="为 CodeCGC 工作流推荐下一条命令",
281
+ argument_hint="[参数]",
282
+ primary_tool="codecgc.route",
283
+ direct_rules=[
284
+ "调用前提取 `flow` 和 `slug`。",
285
+ "当用户已经知道目标工作流,只想得到下一步推荐动作时,使用这个命令。",
286
+ ],
287
+ missing_rules=[
288
+ "如果缺少 `flow`,询问工作流是 `feature` 还是 `issue`。",
289
+ "如果缺少 `slug`,询问工作流 slug。",
290
+ ],
291
+ fallback_command="cgc-route",
292
+ ),
293
+ build_mcp_first_command_template(
294
+ filename="cgc-history.md",
295
+ description="查看最近的 CodeCGC 工作流历史",
296
+ argument_hint="[参数]",
297
+ primary_tool="codecgc.history",
298
+ direct_rules=[
299
+ "映射可选历史筛选字段,如 `flow`、`status`、`last`、`include_fixtures`。",
300
+ "如果没有提供筛选条件,就使用默认历史查询。",
301
+ ],
302
+ fallback_command="cgc-history",
303
+ ),
304
+ build_mcp_first_command_template(
305
+ filename="cgc-package-audit.md",
306
+ description="审计 CodeCGC 发布包运行时内容",
307
+ argument_hint="[参数]",
308
+ primary_tool="codecgc.package_audit",
309
+ direct_rules=[
310
+ "当用户明确要求 `summary` 或 `json` 时,映射 `format`。",
311
+ "该命令用于发布包和运行时完整性检查。",
312
+ ],
313
+ fallback_command="cgc-package-audit",
314
+ ),
315
+ build_mcp_first_command_template(
316
+ filename="cgc-external-audit.md",
317
+ description="审计外部 MCP 能力注册与接入状态",
318
+ argument_hint="[参数]",
319
+ primary_tool="codecgc.external_audit",
320
+ direct_rules=[
321
+ "映射可选字段 `workspace` 和 `format`。",
322
+ "该命令用于外部能力策略与注册检查。",
323
+ ],
324
+ fallback_command="cgc-external-audit",
325
+ ),
326
+ build_mcp_first_command_template(
327
+ filename="cgc-release-readiness.md",
328
+ description="运行 CodeCGC 发布就绪检查",
329
+ argument_hint="[参数]",
330
+ primary_tool="codecgc.release_readiness",
331
+ direct_rules=[
332
+ "映射可选字段 `workspace` 和 `format`。",
333
+ "该命令用于联合检查发布、维护和运维就绪状态。",
334
+ ],
335
+ fallback_command="cgc-release-readiness",
336
+ ),
337
+ build_mcp_first_command_template(
338
+ filename="cgc-lifecycle.md",
339
+ description="审计 CodeCGC 生命周期覆盖情况",
340
+ argument_hint="[参数]",
341
+ primary_tool="codecgc.lifecycle",
342
+ direct_rules=[
343
+ "当用户明确要求 `summary` 或 `json` 时,映射 `format`。",
344
+ "该命令用于检查 roadmap、workflow、execution 的生命周期覆盖情况。",
345
+ ],
346
+ fallback_command="cgc-lifecycle",
347
+ ),
348
+ ]
349
+ )
350
+ return templates
175
351
 
176
352
 
177
353
  def write_custom_command_files(target_dir: Path, bin_dir: Path) -> list[str]:
@@ -219,6 +395,33 @@ def merge_hook_settings(current: dict[str, Any], command_text: str) -> tuple[dic
219
395
  return current, True
220
396
 
221
397
 
398
+ def merge_permission_settings(current: dict[str, Any], allow_rules: list[str]) -> tuple[dict[str, Any], bool]:
399
+ permissions = current.get("permissions")
400
+ changed = False
401
+
402
+ if not isinstance(permissions, dict):
403
+ permissions = {}
404
+ current["permissions"] = permissions
405
+ changed = True
406
+
407
+ allow = permissions.get("allow")
408
+ if not isinstance(allow, list):
409
+ allow = []
410
+ permissions["allow"] = allow
411
+ changed = True
412
+
413
+ existing = {str(item).strip() for item in allow if str(item).strip()}
414
+ for rule in allow_rules:
415
+ normalized = str(rule).strip()
416
+ if not normalized or normalized in existing:
417
+ continue
418
+ allow.append(normalized)
419
+ existing.add(normalized)
420
+ changed = True
421
+
422
+ return current, changed
423
+
424
+
222
425
  def write_json_file(path: Path, payload: dict[str, Any]) -> Path:
223
426
  path.parent.mkdir(parents=True, exist_ok=True)
224
427
  path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
@@ -276,6 +479,17 @@ def settings_have_hook_command(settings: dict[str, Any], command_text: str) -> b
276
479
  return False
277
480
 
278
481
 
482
+ def settings_have_allowed_tools(settings: dict[str, Any], allow_rules: list[str]) -> bool:
483
+ permissions = settings.get("permissions")
484
+ if not isinstance(permissions, dict):
485
+ return False
486
+ allow = permissions.get("allow")
487
+ if not isinstance(allow, list):
488
+ return False
489
+ existing = {str(item).strip() for item in allow if str(item).strip()}
490
+ return all(str(rule).strip() in existing for rule in allow_rules)
491
+
492
+
279
493
  def build_workspace_hook_command(workspace_paths: dict[str, Path]) -> str:
280
494
  return "powershell -ExecutionPolicy Bypass -File .claude/hooks/route-edit.ps1"
281
495
 
@@ -310,8 +524,11 @@ def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
310
524
  settings,
311
525
  build_workspace_hook_command(workspace_paths),
312
526
  )
527
+ merged_settings, permissions_changed = merge_permission_settings(merged_settings, DEFAULT_ALLOWED_TOOLS)
313
528
  if settings_changed or not workspace_paths["settings"].exists():
314
529
  write_json_file(workspace_paths["settings"], merged_settings)
530
+ elif permissions_changed:
531
+ write_json_file(workspace_paths["settings"], merged_settings)
315
532
 
316
533
  if PROJECT_HOOK_PATH.resolve() != workspace_paths["hook_script"].resolve():
317
534
  shutil.copyfile(PROJECT_HOOK_PATH, workspace_paths["hook_script"])
@@ -337,6 +554,7 @@ def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
337
554
  "Repository-local MCP config was synced from the executor registry.",
338
555
  "Project-local model-routing.yaml was synchronized and preserves custom path blocks.",
339
556
  "Claude pre-edit guardrail hook was synchronized into the target workspace.",
557
+ "Claude MCP tool permissions were merged into project .claude/settings.json.",
340
558
  "Project-local Claude slash commands were synchronized into .claude/commands.",
341
559
  "This mode prepares project-level integration surfaces for the selected workspace.",
342
560
  ],
@@ -352,6 +570,7 @@ def preview_user_install(override_root: str = "") -> dict[str, Any]:
352
570
  user_paths = get_user_claude_paths(override_root)
353
571
  user_settings = load_json_file(user_paths["settings"])
354
572
  merged_settings, settings_changed = merge_hook_settings(user_settings, build_user_hook_command(user_paths))
573
+ merged_settings, permissions_changed = merge_permission_settings(merged_settings, DEFAULT_ALLOWED_TOOLS)
355
574
  mcp_config = build_mcp_config()
356
575
  recommended_next_action = f"cgc-install --mode user --user-root {shell_quote(str(user_paths['root']))}"
357
576
  summary = build_mode_summary_payload(
@@ -372,7 +591,7 @@ def preview_user_install(override_root: str = "") -> dict[str, Any]:
372
591
  "commands_dir": str(user_paths["commands_dir"]),
373
592
  },
374
593
  "would_write": {
375
- "settings_changed": settings_changed or not user_paths["settings"].exists(),
594
+ "settings_changed": settings_changed or permissions_changed or not user_paths["settings"].exists(),
376
595
  "mcp_changed": True,
377
596
  "hook_changed": True,
378
597
  "commands_changed": True,
@@ -384,6 +603,7 @@ def preview_user_install(override_root: str = "") -> dict[str, Any]:
384
603
  "notes": [
385
604
  "This mode does not modify user-level Claude files.",
386
605
  "Use this preview to inspect the future user-level integration surface.",
606
+ "The preview includes MCP tool allow rules for codecgc, codex, and gemini servers.",
387
607
  "Current CodeCGC product policy still defaults to project-local installation.",
388
608
  ],
389
609
  "summary": summary,
@@ -398,6 +618,7 @@ def install_user_runtime(override_root: str = "") -> dict[str, Any]:
398
618
 
399
619
  settings = load_json_file(user_paths["settings"])
400
620
  merged_settings, settings_changed = merge_hook_settings(settings, build_user_hook_command(user_paths))
621
+ merged_settings, permissions_changed = merge_permission_settings(merged_settings, DEFAULT_ALLOWED_TOOLS)
401
622
  write_json_file(user_paths["settings"], merged_settings)
402
623
  write_json_file(user_paths["mcp"], build_mcp_config())
403
624
  shutil.copyfile(PROJECT_HOOK_PATH, user_paths["hook_script"])
@@ -420,7 +641,7 @@ def install_user_runtime(override_root: str = "") -> dict[str, Any]:
420
641
  "commands_dir": str(user_paths["commands_dir"]),
421
642
  },
422
643
  "changes": {
423
- "settings_changed": settings_changed or not user_paths["settings"].exists(),
644
+ "settings_changed": settings_changed or permissions_changed or not user_paths["settings"].exists(),
424
645
  "mcp_changed": True,
425
646
  "hook_changed": True,
426
647
  "commands_changed": True,
@@ -429,6 +650,7 @@ def install_user_runtime(override_root: str = "") -> dict[str, Any]:
429
650
  "notes": [
430
651
  "User-level Claude integration files were written to the selected root.",
431
652
  "The user-level hook script was copied from the project hook source.",
653
+ "MCP tool allow rules were merged into ~/.claude/settings.json.",
432
654
  "User-level Claude slash commands were written to ~/.claude/commands.",
433
655
  "This mode is explicit and should be used only when a broader Claude integration surface is intended.",
434
656
  ],
@@ -521,6 +743,7 @@ def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
521
743
  routing_exists = workspace_paths["routing_file"].exists()
522
744
 
523
745
  hook_registered = settings_have_hook_command(current_settings, expected_hook_command)
746
+ permissions_registered = settings_have_allowed_tools(current_settings, DEFAULT_ALLOWED_TOOLS)
524
747
  mcp_matches = current_mcp == expected_mcp if workspace_paths["mcp"].exists() else False
525
748
  hook_file_matches = current_hook_text == expected_hook_text if workspace_paths["hook_script"].exists() else False
526
749
 
@@ -531,6 +754,8 @@ def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
531
754
  missing.append("mcp_json")
532
755
  if not hook_registered:
533
756
  missing.append("claude_settings_hook")
757
+ if not permissions_registered:
758
+ missing.append("claude_settings_permissions")
534
759
  if not hook_file_matches:
535
760
  missing.append("hook_script")
536
761
 
@@ -546,11 +771,13 @@ def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
546
771
  "hook_exists": workspace_paths["hook_script"].exists(),
547
772
  "mcp_matches_expected": mcp_matches,
548
773
  "hook_registered": hook_registered,
774
+ "permissions_registered": permissions_registered,
549
775
  "hook_file_matches_expected": hook_file_matches,
550
776
  "ready": ready,
551
777
  "missing_or_outdated": missing,
552
778
  "recommended_command": "" if ready else build_workspace_install_command(workspace_paths["root"]),
553
779
  "hook_expected": {"hooks": build_hook_payload(expected_hook_command)},
780
+ "permissions_expected": {"permissions": {"allow": DEFAULT_ALLOWED_TOOLS}},
554
781
  }
555
782
 
556
783
 
@@ -563,6 +790,7 @@ def collect_user_status(user_paths: dict[str, Path]) -> dict[str, Any]:
563
790
  current_hook_text = load_text_file(user_paths["hook_script"])
564
791
 
565
792
  hook_registered = settings_have_hook_command(current_settings, expected_hook_command)
793
+ permissions_registered = settings_have_allowed_tools(current_settings, DEFAULT_ALLOWED_TOOLS)
566
794
  mcp_matches = current_mcp == expected_mcp if user_paths["mcp"].exists() else False
567
795
  hook_file_matches = current_hook_text == expected_hook_text if user_paths["hook_script"].exists() else False
568
796
 
@@ -571,6 +799,8 @@ def collect_user_status(user_paths: dict[str, Path]) -> dict[str, Any]:
571
799
  missing.append("mcp_json")
572
800
  if not hook_registered:
573
801
  missing.append("claude_settings_hook")
802
+ if not permissions_registered:
803
+ missing.append("claude_settings_permissions")
574
804
  if not hook_file_matches:
575
805
  missing.append("hook_script")
576
806
 
@@ -585,6 +815,7 @@ def collect_user_status(user_paths: dict[str, Path]) -> dict[str, Any]:
585
815
  "hook_exists": user_paths["hook_script"].exists(),
586
816
  "mcp_matches_expected": mcp_matches,
587
817
  "hook_registered": hook_registered,
818
+ "permissions_registered": permissions_registered,
588
819
  "hook_file_matches_expected": hook_file_matches,
589
820
  "ready": ready,
590
821
  "missing_or_outdated": missing,
@@ -673,18 +904,33 @@ def build_pip_install_command(python_command: str, requirement: str) -> str:
673
904
 
674
905
  def build_local_editable_install_command(python_command: str) -> str:
675
906
  runtime_command = python_command.strip() or sys.executable
907
+ codecgcmcp_path = WORKSPACE / "codecgcmcp"
676
908
  codexmcp_path = WORKSPACE / "codexmcp"
677
909
  geminimcp_path = WORKSPACE / "geminimcp"
910
+ if not (codecgcmcp_path / "pyproject.toml").exists():
911
+ return ""
678
912
  if not (codexmcp_path / "pyproject.toml").exists():
679
913
  return ""
680
914
  if not (geminimcp_path / "pyproject.toml").exists():
681
915
  return ""
682
916
  return (
683
- f"{shell_quote(runtime_command)} -m pip install -e {shell_quote(str(codexmcp_path))} "
917
+ f"{shell_quote(runtime_command)} -m pip install -e {shell_quote(str(codecgcmcp_path))} "
918
+ f"-e {shell_quote(str(codexmcp_path))} "
684
919
  f"-e {shell_quote(str(geminimcp_path))}"
685
920
  )
686
921
 
687
922
 
923
+ def format_bool_zh(value: Any) -> str:
924
+ return "是" if bool(value) else "否"
925
+
926
+
927
+ def format_list_zh(items: Any, empty_text: str = "无") -> str:
928
+ if not isinstance(items, list):
929
+ return empty_text
930
+ values = [str(item).strip() for item in items if str(item).strip()]
931
+ return "、".join(values) if values else empty_text
932
+
933
+
688
934
  def classify_doctor_failures(
689
935
  checks: list[dict[str, Any]],
690
936
  configured_python_command: str,
@@ -753,6 +999,25 @@ def classify_doctor_failures(
753
999
  )
754
1000
  continue
755
1001
 
1002
+ if name == "python_runtime_import_probe_codecgcmcp":
1003
+ if configured_python_missing:
1004
+ continue
1005
+ if "No module named 'codecgcmcp'" in detail or 'No module named "codecgcmcp"' in detail:
1006
+ add_failure(
1007
+ "codecgcmcp-package-missing",
1008
+ "当前解释器无法导入本地 `codecgcmcp` 包。",
1009
+ "确认当前安装包已包含 `codecgcmcp/src`;仓库开发环境可执行本地 editable install,安装产物则应重新安装 CodeCGC 包。",
1010
+ editable_install_command,
1011
+ )
1012
+ else:
1013
+ add_failure(
1014
+ "codecgcmcp-runtime-broken",
1015
+ "`codecgcmcp` 启动入口存在,但当前运行时仍无法导入。",
1016
+ "仓库开发环境可先重装本地编排器包;若你使用的是已安装产物,则优先重新安装 CodeCGC,再检查编排器源码是否缺失或损坏。",
1017
+ editable_install_command,
1018
+ )
1019
+ continue
1020
+
756
1021
  if name == "python_runtime_import_probe_codexmcp":
757
1022
  if configured_python_missing:
758
1023
  continue
@@ -866,11 +1131,18 @@ def collect_doctor_status(override_workspace: str = "") -> dict[str, Any]:
866
1131
 
867
1132
  runtime_probe_command = configured_python_command if configured_python_command else (python_command or sys.executable)
868
1133
  runtime_env = dict(os.environ)
869
- combined_pythonpath = os.pathsep.join([str(WORKSPACE / "codexmcp" / "src"), str(WORKSPACE / "geminimcp" / "src")])
1134
+ combined_pythonpath = os.pathsep.join(
1135
+ [
1136
+ str(WORKSPACE / "scripts"),
1137
+ str(WORKSPACE / "codecgcmcp" / "src"),
1138
+ str(WORKSPACE / "codexmcp" / "src"),
1139
+ str(WORKSPACE / "geminimcp" / "src"),
1140
+ ]
1141
+ )
870
1142
  existing_pythonpath = runtime_env.get("PYTHONPATH", "").strip()
871
1143
  runtime_env["PYTHONPATH"] = f"{combined_pythonpath}{os.pathsep}{existing_pythonpath}" if existing_pythonpath else combined_pythonpath
872
1144
 
873
- for module_name in ("mcp", "codexmcp.cli", "geminimcp.cli"):
1145
+ for module_name in ("mcp", "codecgcmcp.cli", "codexmcp.cli", "geminimcp.cli"):
874
1146
  probe = probe_python_import(runtime_probe_command, module_name, runtime_env)
875
1147
  checks.append(
876
1148
  {
@@ -972,12 +1244,12 @@ def main() -> int:
972
1244
  lines = [
973
1245
  f"- 工作区: {result.get('workspace', '')}",
974
1246
  f"- 范围: {summary.get('scope', '')}",
975
- f"- 项目级就绪: {'是' if summary.get('project_ready') else '否'}",
976
- f"- 用户级就绪: {'是' if summary.get('user_ready') else '否'}",
1247
+ f"- 项目级就绪: {format_bool_zh(summary.get('project_ready'))}",
1248
+ f"- 用户级就绪: {format_bool_zh(summary.get('user_ready'))}",
977
1249
  f"- 策略: {summary.get('default_policy', '')}",
978
1250
  f"- 摘要: {summary.get('human_summary', '')}",
979
- f"- 项目缺失项: {', '.join(project.get('missing_or_outdated', [])) or '无'}",
980
- f"- 用户缺失项: {', '.join(user.get('missing_or_outdated', [])) or '无'}",
1251
+ f"- 项目级缺失项: {format_list_zh(project.get('missing_or_outdated', []))}",
1252
+ f"- 用户级缺失项: {format_list_zh(user.get('missing_or_outdated', []))}",
981
1253
  ]
982
1254
  recommended_project = str(summary.get("recommended_project_command", "")).strip()
983
1255
  recommended_user = str(summary.get("recommended_user_command", "")).strip()
@@ -992,11 +1264,11 @@ def main() -> int:
992
1264
  lines = [
993
1265
  f"- 工作区: {result.get('workspace', '')}",
994
1266
  f"- 范围: {summary.get('scope', '')}",
995
- f"- 就绪: {'是' if summary.get('ready') else '否'}",
1267
+ f"- 就绪: {format_bool_zh(summary.get('ready'))}",
996
1268
  f"- 摘要: {summary.get('human_summary', '')}",
997
1269
  ]
998
1270
  failed_checks = summary.get("failed_checks", [])
999
- lines.append(f"- 失败检查项: {', '.join(str(item) for item in failed_checks) or '无'}")
1271
+ lines.append(f"- 失败检查项: {format_list_zh(failed_checks)}")
1000
1272
  failure_categories = summary.get("failure_categories", [])
1001
1273
  for item in failure_categories:
1002
1274
  if not isinstance(item, dict):
@@ -1013,7 +1285,7 @@ def main() -> int:
1013
1285
  fix_command = str(summary.get("recommended_fix_command", "")).strip()
1014
1286
  runtime_fix_command = str(summary.get("recommended_runtime_fix_command", "")).strip()
1015
1287
  next_actions = [item for item in [runtime_fix_command, fix_command] if item]
1016
- print(render_summary_block("CodeCGC Doctor", lines, next_actions))
1288
+ print(render_summary_block("CodeCGC 自检", lines, next_actions))
1017
1289
  return 0 if result.get("success") else 1
1018
1290
 
1019
1291
  if args.format == "summary" and args.mode in {"local", "user-dry-run", "user"}:
@@ -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