@hunyed15/codecgc 0.1.7 → 0.1.9
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/.claude/hooks/route-edit.ps1 +58 -57
- package/INSTALLATION.md +122 -484
- package/README.md +124 -149
- package/bin/cgc-external-status.js +4 -0
- package/bin/cgc-start.js +4 -0
- package/bin/codecgc.js +141 -20
- package/codecgc/compound/codecgc-capability-matrix.md +37 -0
- package/codecgc/reference/README.md +69 -0
- package/codecgc/reference/execution-model.md +3 -1
- package/codecgc/reference/maintainer-guide.md +93 -0
- package/codecgc/reference/mcp-tool-surface.md +112 -0
- package/codecgc/reference/onboarding.md +69 -0
- package/codecgc/reference/operation-guide.md +29 -23
- package/codecgc/reference/path-contract.md +53 -0
- package/codecgc/reference/policy-routing.md +58 -0
- package/codecgc/reference/project-structure.md +87 -0
- package/codecgc/reference/quickstart.md +110 -0
- package/codecgc/reference/real-workflow-loop.md +123 -0
- package/codecgc/reference/recovery-loop.md +109 -0
- package/codecgc/reference/release-maintenance-playbook.md +4 -1
- package/codecgc/reference/troubleshooting.md +114 -0
- package/codecgc/roadmap/codecgc-release-maintenance/delivery-plan.md +49 -0
- package/codecgc/roadmap/codecgc-release-maintenance/overview.md +41 -0
- package/codecgc/roadmap/codecgc-release-maintenance/phases.md +84 -0
- package/codecgc/templates/claude/settings.local.json +27 -0
- package/codecgc/templates/codex/codecgcrc.json +22 -0
- package/codecgc/templates/gemini/codecgc-policy.toml +47 -0
- package/codecgcmcp/README.md +57 -11
- package/codecgcmcp/src/codecgcmcp/server.py +164 -26
- package/codexmcp/src/codexmcp/server.py +45 -0
- package/geminimcp/src/geminimcp/server.py +106 -24
- package/model-routing.yaml +31 -6
- package/package.json +12 -4
- package/scripts/audit_codecgc_external_capabilities.py +83 -4
- package/scripts/audit_codecgc_historical_audits.py +42 -2
- package/scripts/audit_codecgc_package_runtime.py +76 -4
- package/scripts/audit_codecgc_release_readiness.py +55 -3
- package/scripts/audit_codecgc_workflow_history.py +8 -5
- package/scripts/build_codecgc_task.py +69 -45
- package/scripts/codecgc_artifact_roots.py +8 -40
- package/scripts/codecgc_console_io.py +3 -45
- package/scripts/codecgc_executor_registry.py +4 -54
- package/scripts/codecgc_path_contract.py +7 -0
- package/scripts/codecgc_policy.py +447 -0
- package/scripts/codecgc_routing_paths.py +3 -16
- package/scripts/codecgc_routing_template.py +11 -135
- package/scripts/codecgc_runtime/__init__.py +1 -0
- package/scripts/codecgc_runtime/artifacts.py +42 -0
- package/scripts/codecgc_runtime/console.py +45 -0
- package/scripts/codecgc_runtime/executor_registry.py +55 -0
- package/scripts/codecgc_runtime/mcp_config.py +72 -0
- package/scripts/codecgc_runtime/path_contract.py +123 -0
- package/scripts/codecgc_runtime/paths.py +22 -0
- package/scripts/codecgc_runtime/routing_paths.py +16 -0
- package/scripts/codecgc_runtime/routing_template.py +171 -0
- package/scripts/codecgc_runtime/workflow_runtime.py +72 -0
- package/scripts/codecgc_runtime_paths.py +3 -22
- package/scripts/codecgc_step_control.py +3 -2
- package/scripts/codecgc_workflow_runtime.py +3 -71
- package/scripts/entry_codecgc_workflow.py +4 -0
- package/scripts/install_codecgc.py +560 -32
- package/scripts/normalize_codecgc_audits.py +5 -3
- package/scripts/postinstall_codecgc.js +6 -56
- package/scripts/review_codecgc_workflow.py +6 -3
- package/scripts/route_codecgc_workflow.py +67 -36
- package/scripts/run_codecgc_build.py +28 -0
- package/scripts/run_codecgc_fix.py +28 -0
- package/scripts/run_codecgc_task.py +5 -2
- package/scripts/run_codecgc_test.py +28 -0
- package/scripts/sync_codecgc_mcp_config.py +4 -54
- package/scripts/write_codecgc_review.py +7 -3
|
@@ -10,6 +10,7 @@ from typing import Any
|
|
|
10
10
|
from codecgc_console_io import render_summary_block
|
|
11
11
|
from codecgc_executor_registry import build_executor_registry
|
|
12
12
|
from codecgc_executor_registry import resolve_python_command
|
|
13
|
+
from codecgc_policy import load_policy
|
|
13
14
|
from codecgc_routing_template import sync_workspace_routing_file
|
|
14
15
|
from codecgc_runtime_paths import PACKAGE_ROOT
|
|
15
16
|
from codecgc_runtime_paths import resolve_workspace_root
|
|
@@ -24,12 +25,20 @@ SETTINGS_PATH = CLAUDE_DIR / "settings.json"
|
|
|
24
25
|
MCP_CONFIG_PATH = WORKSPACE / ".mcp.json"
|
|
25
26
|
PROJECT_HOOK_PATH = HOOKS_DIR / "route-edit.ps1"
|
|
26
27
|
PROJECT_ROUTING_PATH = WORKSPACE / "model-routing.yaml"
|
|
28
|
+
PROJECT_TEMPLATES_DIR = WORKSPACE / "codecgc" / "templates"
|
|
29
|
+
EDIT_GUARDRAIL_MATCHER = "Edit|Write|MultiEdit|Bash|PowerShell"
|
|
30
|
+
LEGACY_EDIT_GUARDRAIL_MATCHERS = {"Edit|Write", "Edit|Write|MultiEdit"}
|
|
31
|
+
PROJECT_ONBOARDING_RELATIVE_PATH = "codecgc/START_HERE.md"
|
|
32
|
+
PROJECT_ONBOARDING_MARKER = "<!-- codecgc:onboarding:v1 -->"
|
|
33
|
+
CLAUDE_SETTINGS_TEMPLATE = PROJECT_TEMPLATES_DIR / "claude" / "settings.local.json"
|
|
34
|
+
CODEX_POLICY_TEMPLATE = PROJECT_TEMPLATES_DIR / "codex" / "codecgcrc.json"
|
|
35
|
+
GEMINI_POLICY_TEMPLATE = PROJECT_TEMPLATES_DIR / "gemini" / "codecgc-policy.toml"
|
|
27
36
|
|
|
28
37
|
|
|
29
38
|
DEFAULT_HOOKS = {
|
|
30
39
|
"PreToolUse": [
|
|
31
40
|
{
|
|
32
|
-
"matcher":
|
|
41
|
+
"matcher": EDIT_GUARDRAIL_MATCHER,
|
|
33
42
|
"hooks": [
|
|
34
43
|
{
|
|
35
44
|
"type": "command",
|
|
@@ -48,6 +57,22 @@ DEFAULT_ALLOWED_TOOLS = [
|
|
|
48
57
|
"mcp__gemini__*",
|
|
49
58
|
]
|
|
50
59
|
|
|
60
|
+
PROJECT_WORKFLOW_DIRS = [
|
|
61
|
+
"codecgc/features",
|
|
62
|
+
"codecgc/issues",
|
|
63
|
+
"codecgc/execution",
|
|
64
|
+
"codecgc/requirements",
|
|
65
|
+
"codecgc/architecture",
|
|
66
|
+
"codecgc/roadmap",
|
|
67
|
+
"codecgc/compound",
|
|
68
|
+
"codecgc/docs",
|
|
69
|
+
"codecgc/reference",
|
|
70
|
+
"codecgc/fixtures/features",
|
|
71
|
+
"codecgc/fixtures/issues",
|
|
72
|
+
"codecgc/fixtures/execution",
|
|
73
|
+
"codecgc/fixtures/roadmap",
|
|
74
|
+
]
|
|
75
|
+
|
|
51
76
|
|
|
52
77
|
def get_user_claude_root(override_root: str = "") -> Path:
|
|
53
78
|
override = override_root.strip() or os.environ.get("CODECGC_USER_CLAUDE_DIR", "").strip()
|
|
@@ -77,15 +102,24 @@ def get_workspace_paths(override_workspace: str = "") -> dict[str, Path]:
|
|
|
77
102
|
root = resolve_workspace_root(override_workspace)
|
|
78
103
|
claude_dir = root / ".claude"
|
|
79
104
|
hooks_dir = claude_dir / "hooks"
|
|
105
|
+
codex_dir = root / ".codex"
|
|
106
|
+
gemini_dir = root / ".gemini"
|
|
80
107
|
return {
|
|
81
108
|
"root": root,
|
|
82
109
|
"claude_dir": claude_dir,
|
|
83
110
|
"hooks_dir": hooks_dir,
|
|
84
|
-
"settings": claude_dir / "settings.json",
|
|
111
|
+
"settings": claude_dir / "settings.local.json",
|
|
112
|
+
"legacy_settings": claude_dir / "settings.json",
|
|
85
113
|
"mcp": root / ".mcp.json",
|
|
86
114
|
"hook_script": hooks_dir / "route-edit.ps1",
|
|
87
115
|
"commands_dir": claude_dir / "commands",
|
|
88
116
|
"routing_file": root / "model-routing.yaml",
|
|
117
|
+
"onboarding_file": root / PROJECT_ONBOARDING_RELATIVE_PATH,
|
|
118
|
+
"codex_dir": codex_dir,
|
|
119
|
+
"codex_policy": codex_dir / "codecgcrc.json",
|
|
120
|
+
"gemini_dir": gemini_dir,
|
|
121
|
+
"gemini_policies_dir": gemini_dir / "policies",
|
|
122
|
+
"gemini_policy": gemini_dir / "policies" / "codecgc-policy.toml",
|
|
89
123
|
}
|
|
90
124
|
|
|
91
125
|
|
|
@@ -101,11 +135,130 @@ def load_text_file(path: Path) -> str:
|
|
|
101
135
|
return path.read_text(encoding="utf-8")
|
|
102
136
|
|
|
103
137
|
|
|
138
|
+
def copy_template_file(template_path: Path, target_path: Path) -> Path:
|
|
139
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
shutil.copyfile(template_path, target_path)
|
|
141
|
+
return target_path
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def ensure_workspace_workflow_dirs(workspace_root: Path) -> list[str]:
|
|
145
|
+
created_or_existing: list[str] = []
|
|
146
|
+
for relative_path in PROJECT_WORKFLOW_DIRS:
|
|
147
|
+
directory = workspace_root / relative_path
|
|
148
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
149
|
+
created_or_existing.append(str(directory))
|
|
150
|
+
return created_or_existing
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def workspace_workflow_dirs_ready(workspace_root: Path) -> bool:
|
|
154
|
+
return all((workspace_root / relative_path).is_dir() for relative_path in PROJECT_WORKFLOW_DIRS)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def build_project_onboarding_text() -> str:
|
|
158
|
+
return f"""{PROJECT_ONBOARDING_MARKER}
|
|
159
|
+
# CodeCGC Start Here
|
|
160
|
+
|
|
161
|
+
This file is generated by `cgc-install` for this project. It is intentionally project-relative and should not contain machine-specific install paths.
|
|
162
|
+
|
|
163
|
+
## First Run
|
|
164
|
+
|
|
165
|
+
Inside Claude:
|
|
166
|
+
|
|
167
|
+
```text
|
|
168
|
+
/cgc-start
|
|
169
|
+
/cgc-status
|
|
170
|
+
/cgc-doctor
|
|
171
|
+
/cgc 新增一个登录页面,放在 src/components/LoginForm.tsx
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Outside Claude:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
cgc-start
|
|
178
|
+
cgc-status
|
|
179
|
+
cgc-doctor
|
|
180
|
+
cgc "新增一个登录页面,放在 src/components/LoginForm.tsx"
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## What CodeCGC Owns
|
|
184
|
+
|
|
185
|
+
- Claude owns orchestration, requirements, design notes, docs, review, acceptance, and workflow state.
|
|
186
|
+
- Codex owns backend implementation and backend tests.
|
|
187
|
+
- Gemini owns frontend implementation and frontend tests.
|
|
188
|
+
- Mixed backend/frontend work should be split before execution.
|
|
189
|
+
|
|
190
|
+
## Installed Project Surface
|
|
191
|
+
|
|
192
|
+
```text
|
|
193
|
+
.mcp.json
|
|
194
|
+
model-routing.yaml
|
|
195
|
+
.claude/settings.local.json
|
|
196
|
+
.claude/hooks/route-edit.ps1
|
|
197
|
+
.claude/commands/cgc*.md
|
|
198
|
+
.codex/codecgcrc.json
|
|
199
|
+
.gemini/policies/codecgc-policy.toml
|
|
200
|
+
codecgc/features/
|
|
201
|
+
codecgc/issues/
|
|
202
|
+
codecgc/execution/
|
|
203
|
+
codecgc/requirements/
|
|
204
|
+
codecgc/architecture/
|
|
205
|
+
codecgc/roadmap/
|
|
206
|
+
codecgc/compound/
|
|
207
|
+
codecgc/docs/
|
|
208
|
+
codecgc/reference/
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Normal Loop
|
|
212
|
+
|
|
213
|
+
1. Start with `/cgc <your request>` or `cgc "<your request>"`.
|
|
214
|
+
2. Let CodeCGC decide whether the next step is planning, execution, review, or closure.
|
|
215
|
+
3. Review execution audits with `/cgc-review` or `cgc-review`.
|
|
216
|
+
4. Use `/cgc-history` or `cgc-history` when you need to find open work.
|
|
217
|
+
|
|
218
|
+
## Recovery
|
|
219
|
+
|
|
220
|
+
- If project integration looks wrong, run `cgc-install`, then `cgc-status`.
|
|
221
|
+
- If runtime or executor startup fails, run `cgc-doctor`.
|
|
222
|
+
- If a write is blocked, inspect `model-routing.yaml`; it is the project-local policy source of truth.
|
|
223
|
+
- If an execution was rejected, use `/cgc` or `cgc` to continue the same workflow rather than starting a new one.
|
|
224
|
+
|
|
225
|
+
## Stable References
|
|
226
|
+
|
|
227
|
+
- `codecgc/reference/quickstart.md`
|
|
228
|
+
- `codecgc/reference/onboarding.md`
|
|
229
|
+
- `codecgc/reference/operation-guide.md`
|
|
230
|
+
- `codecgc/reference/troubleshooting.md`
|
|
231
|
+
- `codecgc/reference/path-contract.md`
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def write_project_onboarding_file(workspace_root: Path) -> Path:
|
|
236
|
+
path = workspace_root / PROJECT_ONBOARDING_RELATIVE_PATH
|
|
237
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
238
|
+
path.write_text(build_project_onboarding_text(), encoding="utf-8")
|
|
239
|
+
return path
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def onboarding_file_is_valid(path: Path) -> bool:
|
|
243
|
+
text = load_text_file(path)
|
|
244
|
+
if not text:
|
|
245
|
+
return False
|
|
246
|
+
required_markers = [
|
|
247
|
+
PROJECT_ONBOARDING_MARKER,
|
|
248
|
+
"/cgc-start",
|
|
249
|
+
"cgc-start",
|
|
250
|
+
"/cgc-status",
|
|
251
|
+
"cgc-status",
|
|
252
|
+
"model-routing.yaml",
|
|
253
|
+
]
|
|
254
|
+
return all(marker in text for marker in required_markers)
|
|
255
|
+
|
|
256
|
+
|
|
104
257
|
def build_hook_payload(command_text: str) -> dict[str, Any]:
|
|
105
258
|
return {
|
|
106
259
|
"PreToolUse": [
|
|
107
260
|
{
|
|
108
|
-
"matcher":
|
|
261
|
+
"matcher": EDIT_GUARDRAIL_MATCHER,
|
|
109
262
|
"hooks": [
|
|
110
263
|
{
|
|
111
264
|
"type": "command",
|
|
@@ -117,6 +270,14 @@ def build_hook_payload(command_text: str) -> dict[str, Any]:
|
|
|
117
270
|
}
|
|
118
271
|
|
|
119
272
|
|
|
273
|
+
def policy_file_is_valid(path: Path) -> bool:
|
|
274
|
+
try:
|
|
275
|
+
load_policy(path)
|
|
276
|
+
except Exception:
|
|
277
|
+
return False
|
|
278
|
+
return True
|
|
279
|
+
|
|
280
|
+
|
|
120
281
|
def _normalize_command_path_for_markdown(path: Path) -> str:
|
|
121
282
|
return str(path).replace("\\", "\\\\")
|
|
122
283
|
|
|
@@ -323,6 +484,17 @@ def build_custom_command_templates(bin_dir: Path) -> dict[str, str]:
|
|
|
323
484
|
],
|
|
324
485
|
fallback_command="cgc-external-audit",
|
|
325
486
|
),
|
|
487
|
+
build_mcp_first_command_template(
|
|
488
|
+
filename="cgc-external-status.md",
|
|
489
|
+
description="查看外部 MCP 能力状态面板",
|
|
490
|
+
argument_hint="[参数]",
|
|
491
|
+
primary_tool="codecgc.external_status",
|
|
492
|
+
direct_rules=[
|
|
493
|
+
"映射可选字段 `workspace` 和 `format`。",
|
|
494
|
+
"该命令用于日常快速查看外部能力登记状态与本地 MCP 观测结果。",
|
|
495
|
+
],
|
|
496
|
+
fallback_command="cgc-external-status",
|
|
497
|
+
),
|
|
326
498
|
build_mcp_first_command_template(
|
|
327
499
|
filename="cgc-release-readiness.md",
|
|
328
500
|
description="运行 CodeCGC 发布就绪检查",
|
|
@@ -347,6 +519,57 @@ def build_custom_command_templates(bin_dir: Path) -> dict[str, str]:
|
|
|
347
519
|
),
|
|
348
520
|
]
|
|
349
521
|
)
|
|
522
|
+
templates["cgc-install.md"] = """---
|
|
523
|
+
description: Install or repair CodeCGC project-local integration
|
|
524
|
+
argument-hint: "[optional install arguments]"
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
Use the `codecgc.install` MCP tool as the primary path.
|
|
528
|
+
|
|
529
|
+
Default behavior:
|
|
530
|
+
|
|
531
|
+
- Run project-local install for the current workspace.
|
|
532
|
+
- Do not install user-level Claude files unless the user explicitly asks for `--mode user`.
|
|
533
|
+
- Treat `model-routing.yaml` as the single routing policy source.
|
|
534
|
+
|
|
535
|
+
Argument mapping:
|
|
536
|
+
|
|
537
|
+
- No arguments: call `codecgc.install` with `mode: "local"`.
|
|
538
|
+
- `status`: call `codecgc.install` with `mode: "status"`.
|
|
539
|
+
- `doctor`: call `codecgc.install` with `mode: "doctor"`.
|
|
540
|
+
- `--workspace <dir>`: pass the workspace through.
|
|
541
|
+
- `--mode user-dry-run`: preview user-level files only.
|
|
542
|
+
- `--mode user`: only run if the user explicitly asks to write user-level Claude integration.
|
|
543
|
+
|
|
544
|
+
Fallback:
|
|
545
|
+
|
|
546
|
+
- If the MCP tool is unavailable, run `cgc-install` with the same arguments from the target project root.
|
|
547
|
+
- After install, run or suggest `cgc-start`, `cgc-status`, and `cgc-doctor`.
|
|
548
|
+
"""
|
|
549
|
+
templates["cgc-start.md"] = """---
|
|
550
|
+
description: Show the CodeCGC first-run entry guide for this project
|
|
551
|
+
argument-hint: "[optional workspace]"
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
Use `codecgc.start` as the primary path.
|
|
555
|
+
|
|
556
|
+
Default behavior:
|
|
557
|
+
|
|
558
|
+
- Treat this as a read-only onboarding/status entry.
|
|
559
|
+
- Summarize the project-local `codecgc/START_HERE.md` guide if present.
|
|
560
|
+
- If the guide is missing, tell the user to run `/cgc-install`.
|
|
561
|
+
- Keep the answer short and in Chinese.
|
|
562
|
+
|
|
563
|
+
Argument mapping:
|
|
564
|
+
|
|
565
|
+
- No arguments: call `codecgc.start`.
|
|
566
|
+
- `--workspace <dir>`: pass the workspace through.
|
|
567
|
+
|
|
568
|
+
Fallback:
|
|
569
|
+
|
|
570
|
+
- If the MCP tool is unavailable, run `cgc-start` from the target project root.
|
|
571
|
+
- If `cgc-start` reports missing onboarding, suggest `cgc-install`.
|
|
572
|
+
"""
|
|
350
573
|
return templates
|
|
351
574
|
|
|
352
575
|
|
|
@@ -361,6 +584,15 @@ def write_custom_command_files(target_dir: Path, bin_dir: Path) -> list[str]:
|
|
|
361
584
|
return written
|
|
362
585
|
|
|
363
586
|
|
|
587
|
+
def is_codecgc_route_edit_hook(hook: Any) -> bool:
|
|
588
|
+
if not isinstance(hook, dict):
|
|
589
|
+
return False
|
|
590
|
+
if hook.get("type") != "command":
|
|
591
|
+
return False
|
|
592
|
+
command = str(hook.get("command", "")).replace("\\", "/").lower()
|
|
593
|
+
return "route-edit.ps1" in command
|
|
594
|
+
|
|
595
|
+
|
|
364
596
|
def merge_hook_settings(current: dict[str, Any], command_text: str) -> tuple[dict[str, Any], bool]:
|
|
365
597
|
hooks = current.get("hooks")
|
|
366
598
|
expected_hooks = build_hook_payload(command_text)
|
|
@@ -373,6 +605,23 @@ def merge_hook_settings(current: dict[str, Any], command_text: str) -> tuple[dic
|
|
|
373
605
|
hooks["PreToolUse"] = expected_hooks["PreToolUse"]
|
|
374
606
|
return current, True
|
|
375
607
|
|
|
608
|
+
changed = False
|
|
609
|
+
for item in list(pre_tool_use):
|
|
610
|
+
if not isinstance(item, dict):
|
|
611
|
+
continue
|
|
612
|
+
if item.get("matcher") not in LEGACY_EDIT_GUARDRAIL_MATCHERS | {EDIT_GUARDRAIL_MATCHER}:
|
|
613
|
+
continue
|
|
614
|
+
hook_list = item.get("hooks")
|
|
615
|
+
if not isinstance(hook_list, list):
|
|
616
|
+
continue
|
|
617
|
+
filtered_hooks = [hook for hook in hook_list if not is_codecgc_route_edit_hook(hook)]
|
|
618
|
+
if len(filtered_hooks) != len(hook_list):
|
|
619
|
+
changed = True
|
|
620
|
+
if filtered_hooks:
|
|
621
|
+
item["hooks"] = filtered_hooks
|
|
622
|
+
else:
|
|
623
|
+
pre_tool_use.remove(item)
|
|
624
|
+
|
|
376
625
|
expected = expected_hooks["PreToolUse"][0]
|
|
377
626
|
for item in pre_tool_use:
|
|
378
627
|
if not isinstance(item, dict):
|
|
@@ -387,7 +636,7 @@ def merge_hook_settings(current: dict[str, Any], command_text: str) -> tuple[dic
|
|
|
387
636
|
if not isinstance(hook, dict):
|
|
388
637
|
continue
|
|
389
638
|
if hook.get("type") == "command" and hook.get("command") == expected["hooks"][0]["command"]:
|
|
390
|
-
return current,
|
|
639
|
+
return current, changed
|
|
391
640
|
hook_list.append(expected["hooks"][0])
|
|
392
641
|
return current, True
|
|
393
642
|
|
|
@@ -428,6 +677,16 @@ def write_json_file(path: Path, payload: dict[str, Any]) -> Path:
|
|
|
428
677
|
return path
|
|
429
678
|
|
|
430
679
|
|
|
680
|
+
def build_project_claude_settings(workspace_paths: dict[str, Path]) -> dict[str, Any]:
|
|
681
|
+
settings = load_json_file(CLAUDE_SETTINGS_TEMPLATE)
|
|
682
|
+
merged_settings, _ = merge_hook_settings(
|
|
683
|
+
settings,
|
|
684
|
+
build_workspace_hook_command(workspace_paths),
|
|
685
|
+
)
|
|
686
|
+
merged_settings, _ = merge_permission_settings(merged_settings, DEFAULT_ALLOWED_TOOLS)
|
|
687
|
+
return merged_settings
|
|
688
|
+
|
|
689
|
+
|
|
431
690
|
def shell_quote(value: str) -> str:
|
|
432
691
|
text = str(value)
|
|
433
692
|
if not text:
|
|
@@ -466,7 +725,7 @@ def settings_have_hook_command(settings: dict[str, Any], command_text: str) -> b
|
|
|
466
725
|
for item in pre_tool_use:
|
|
467
726
|
if not isinstance(item, dict):
|
|
468
727
|
continue
|
|
469
|
-
if item.get("matcher") !=
|
|
728
|
+
if item.get("matcher") != EDIT_GUARDRAIL_MATCHER:
|
|
470
729
|
continue
|
|
471
730
|
hook_list = item.get("hooks")
|
|
472
731
|
if not isinstance(hook_list, list):
|
|
@@ -490,8 +749,110 @@ def settings_have_allowed_tools(settings: dict[str, Any], allow_rules: list[str]
|
|
|
490
749
|
return all(str(rule).strip() in existing for rule in allow_rules)
|
|
491
750
|
|
|
492
751
|
|
|
752
|
+
RUNTIME_MCP_SERVER_SPECS: dict[str, dict[str, Any]] = {
|
|
753
|
+
"codecgc": {
|
|
754
|
+
"module": "codecgcmcp.cli",
|
|
755
|
+
"pythonpath_suffixes": ["scripts", "codecgcmcp/src"],
|
|
756
|
+
},
|
|
757
|
+
"codex": {
|
|
758
|
+
"module": "codexmcp.cli",
|
|
759
|
+
"pythonpath_suffixes": ["scripts", "codecgcmcp/src", "codexmcp/src"],
|
|
760
|
+
},
|
|
761
|
+
"gemini": {
|
|
762
|
+
"module": "geminimcp.cli",
|
|
763
|
+
"pythonpath_suffixes": ["scripts", "codecgcmcp/src", "geminimcp/src"],
|
|
764
|
+
},
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
def _normalize_path_parts(path_text: str) -> tuple[str, ...]:
|
|
769
|
+
text = str(path_text).replace("\\", "/").strip().rstrip("/")
|
|
770
|
+
if not text:
|
|
771
|
+
return ()
|
|
772
|
+
return tuple(part.lower() for part in text.split("/") if part)
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def _match_runtime_root(path_text: str, suffix: str) -> tuple[str, ...] | None:
|
|
776
|
+
parts = _normalize_path_parts(path_text)
|
|
777
|
+
suffix_parts = _normalize_path_parts(suffix)
|
|
778
|
+
if not parts or not suffix_parts or len(parts) < len(suffix_parts):
|
|
779
|
+
return None
|
|
780
|
+
if parts[-len(suffix_parts) :] != suffix_parts:
|
|
781
|
+
return None
|
|
782
|
+
return parts[: -len(suffix_parts)]
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def _collect_runtime_roots(entries: list[str], suffix: str) -> set[tuple[str, ...]]:
|
|
786
|
+
roots: set[tuple[str, ...]] = set()
|
|
787
|
+
for entry in entries:
|
|
788
|
+
root = _match_runtime_root(entry, suffix)
|
|
789
|
+
if root is not None:
|
|
790
|
+
roots.add(root)
|
|
791
|
+
return roots
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
def mcp_server_matches_runtime_shape(server_payload: dict[str, Any], spec: dict[str, Any]) -> bool:
|
|
795
|
+
command_text = str(server_payload.get("command", "")).strip()
|
|
796
|
+
if not command_text:
|
|
797
|
+
return False
|
|
798
|
+
|
|
799
|
+
args = server_payload.get("args")
|
|
800
|
+
if args != ["-m", spec["module"]]:
|
|
801
|
+
return False
|
|
802
|
+
|
|
803
|
+
env = server_payload.get("env")
|
|
804
|
+
if not isinstance(env, dict):
|
|
805
|
+
return False
|
|
806
|
+
|
|
807
|
+
workspace_text = str(env.get("CODECGC_WORKSPACE_ROOT", "")).strip()
|
|
808
|
+
if not workspace_text:
|
|
809
|
+
return False
|
|
810
|
+
|
|
811
|
+
pythonpath_text = str(env.get("PYTHONPATH", "")).strip()
|
|
812
|
+
if not pythonpath_text:
|
|
813
|
+
return False
|
|
814
|
+
|
|
815
|
+
entries = [item.strip() for item in pythonpath_text.split(os.pathsep) if item.strip()]
|
|
816
|
+
if not entries:
|
|
817
|
+
return False
|
|
818
|
+
|
|
819
|
+
candidate_roots: set[tuple[str, ...]] | None = None
|
|
820
|
+
for suffix in spec["pythonpath_suffixes"]:
|
|
821
|
+
matching_roots = _collect_runtime_roots(entries, str(suffix))
|
|
822
|
+
if not matching_roots:
|
|
823
|
+
return False
|
|
824
|
+
candidate_roots = matching_roots if candidate_roots is None else candidate_roots & matching_roots
|
|
825
|
+
if not candidate_roots:
|
|
826
|
+
return False
|
|
827
|
+
return True
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
def mcp_config_matches_runtime_shape(payload: dict[str, Any]) -> bool:
|
|
831
|
+
servers = payload.get("mcpServers")
|
|
832
|
+
if not isinstance(servers, dict):
|
|
833
|
+
return False
|
|
834
|
+
|
|
835
|
+
for server_name, spec in RUNTIME_MCP_SERVER_SPECS.items():
|
|
836
|
+
server_payload = servers.get(server_name)
|
|
837
|
+
if not isinstance(server_payload, dict):
|
|
838
|
+
return False
|
|
839
|
+
if not mcp_server_matches_runtime_shape(server_payload, spec):
|
|
840
|
+
return False
|
|
841
|
+
return True
|
|
842
|
+
|
|
843
|
+
|
|
493
844
|
def build_workspace_hook_command(workspace_paths: dict[str, Path]) -> str:
|
|
494
|
-
|
|
845
|
+
package_root = str(WORKSPACE).replace("'", "''")
|
|
846
|
+
workspace_root_path = Path(workspace_paths["root"])
|
|
847
|
+
hook_script_path = workspace_paths.get("hook_script", workspace_root_path / ".claude" / "hooks" / "route-edit.ps1")
|
|
848
|
+
workspace_root = str(workspace_root_path).replace("'", "''")
|
|
849
|
+
hook_script = str(hook_script_path).replace("\\", "/").replace("'", "''")
|
|
850
|
+
return (
|
|
851
|
+
"powershell -ExecutionPolicy Bypass -Command "
|
|
852
|
+
f"\"$env:CODECGC_PACKAGE_ROOT='{package_root}'; "
|
|
853
|
+
f"$env:CODECGC_WORKSPACE_ROOT='{workspace_root}'; "
|
|
854
|
+
f"& '{hook_script}'\""
|
|
855
|
+
)
|
|
495
856
|
|
|
496
857
|
|
|
497
858
|
def build_mode_summary_payload(
|
|
@@ -513,31 +874,29 @@ def build_mode_summary_payload(
|
|
|
513
874
|
|
|
514
875
|
def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
|
|
515
876
|
workspace_paths = get_workspace_paths(override_workspace)
|
|
516
|
-
mcp_path = write_mcp_config(workspace_paths["mcp"])
|
|
877
|
+
mcp_path = write_mcp_config(workspace_paths["mcp"], workspace_paths["root"])
|
|
517
878
|
routing_path = sync_workspace_routing_file(workspace_paths["routing_file"])
|
|
518
879
|
|
|
519
880
|
workspace_paths["claude_dir"].mkdir(parents=True, exist_ok=True)
|
|
520
881
|
workspace_paths["hooks_dir"].mkdir(parents=True, exist_ok=True)
|
|
882
|
+
workflow_dirs = ensure_workspace_workflow_dirs(workspace_paths["root"])
|
|
521
883
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
build_workspace_hook_command(workspace_paths),
|
|
884
|
+
write_json_file(
|
|
885
|
+
workspace_paths["settings"],
|
|
886
|
+
build_project_claude_settings(workspace_paths),
|
|
526
887
|
)
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
write_json_file(workspace_paths["settings"], merged_settings)
|
|
530
|
-
elif permissions_changed:
|
|
531
|
-
write_json_file(workspace_paths["settings"], merged_settings)
|
|
888
|
+
codex_policy_path = copy_template_file(CODEX_POLICY_TEMPLATE, workspace_paths["codex_policy"])
|
|
889
|
+
gemini_policy_path = copy_template_file(GEMINI_POLICY_TEMPLATE, workspace_paths["gemini_policy"])
|
|
532
890
|
|
|
533
891
|
if PROJECT_HOOK_PATH.resolve() != workspace_paths["hook_script"].resolve():
|
|
534
892
|
shutil.copyfile(PROJECT_HOOK_PATH, workspace_paths["hook_script"])
|
|
535
893
|
written_commands = write_custom_command_files(workspace_paths["commands_dir"], WORKSPACE / "bin")
|
|
894
|
+
onboarding_file = write_project_onboarding_file(workspace_paths["root"])
|
|
536
895
|
|
|
537
896
|
summary = build_mode_summary_payload(
|
|
538
897
|
scope="项目级 Claude 与 MCP 集成面",
|
|
539
898
|
human_summary="项目级 CodeCGC 集成文件已同步。",
|
|
540
|
-
recommended_next_action="cgc-
|
|
899
|
+
recommended_next_action="cgc-start",
|
|
541
900
|
)
|
|
542
901
|
|
|
543
902
|
return {
|
|
@@ -547,15 +906,23 @@ def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
|
|
|
547
906
|
"mcp_config": str(mcp_path),
|
|
548
907
|
"routing_file": str(routing_path),
|
|
549
908
|
"claude_settings": str(workspace_paths["settings"]),
|
|
909
|
+
"codex_policy": str(codex_policy_path),
|
|
910
|
+
"gemini_policy": str(gemini_policy_path),
|
|
550
911
|
"hook_script": str(workspace_paths["hook_script"]),
|
|
551
912
|
"commands_dir": str(workspace_paths["commands_dir"]),
|
|
913
|
+
"onboarding_file": str(onboarding_file),
|
|
914
|
+
"workflow_dirs": workflow_dirs,
|
|
552
915
|
"command_files": written_commands,
|
|
553
916
|
"notes": [
|
|
554
917
|
"Repository-local MCP config was synced from the executor registry.",
|
|
555
|
-
"Project-local model-routing.yaml was synchronized
|
|
918
|
+
"Project-local model-routing.yaml was synchronized as the policy source of truth.",
|
|
919
|
+
"Project-local codecgc workflow directories were initialized.",
|
|
556
920
|
"Claude pre-edit guardrail hook was synchronized into the target workspace.",
|
|
557
|
-
"Claude
|
|
921
|
+
"Claude project permissions were rendered from codecgc/templates/claude/settings.local.json.",
|
|
922
|
+
"Project-local Codex policy contract was synchronized into .codex/codecgcrc.json.",
|
|
923
|
+
"Project-local Gemini policy was synchronized into .gemini/policies/codecgc-policy.toml.",
|
|
558
924
|
"Project-local Claude slash commands were synchronized into .claude/commands.",
|
|
925
|
+
"Project-local codecgc/START_HERE.md was written as the first-run entry guide.",
|
|
559
926
|
"This mode prepares project-level integration surfaces for the selected workspace.",
|
|
560
927
|
],
|
|
561
928
|
"summary": summary,
|
|
@@ -563,7 +930,15 @@ def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
|
|
|
563
930
|
|
|
564
931
|
|
|
565
932
|
def build_user_hook_command(user_paths: dict[str, Path]) -> str:
|
|
566
|
-
|
|
933
|
+
package_root = str(WORKSPACE).replace("'", "''")
|
|
934
|
+
workspace_root = str(WORKSPACE).replace("'", "''")
|
|
935
|
+
hook_script = str(user_paths["hook_script"]).replace("'", "''")
|
|
936
|
+
return (
|
|
937
|
+
"powershell -ExecutionPolicy Bypass -Command "
|
|
938
|
+
f"\"$env:CODECGC_PACKAGE_ROOT='{package_root}'; "
|
|
939
|
+
f"$env:CODECGC_WORKSPACE_ROOT='{workspace_root}'; "
|
|
940
|
+
f"& '{hook_script}'\""
|
|
941
|
+
)
|
|
567
942
|
|
|
568
943
|
|
|
569
944
|
def preview_user_install(override_root: str = "") -> dict[str, Any]:
|
|
@@ -571,8 +946,8 @@ def preview_user_install(override_root: str = "") -> dict[str, Any]:
|
|
|
571
946
|
user_settings = load_json_file(user_paths["settings"])
|
|
572
947
|
merged_settings, settings_changed = merge_hook_settings(user_settings, build_user_hook_command(user_paths))
|
|
573
948
|
merged_settings, permissions_changed = merge_permission_settings(merged_settings, DEFAULT_ALLOWED_TOOLS)
|
|
574
|
-
mcp_config = build_mcp_config()
|
|
575
|
-
recommended_next_action =
|
|
949
|
+
mcp_config = build_mcp_config(WORKSPACE)
|
|
950
|
+
recommended_next_action = "cgc-install"
|
|
576
951
|
summary = build_mode_summary_payload(
|
|
577
952
|
scope="用户级 Claude 集成预演",
|
|
578
953
|
human_summary="已完成用户级 Claude 集成预演,未写入任何文件。",
|
|
@@ -602,7 +977,7 @@ def preview_user_install(override_root: str = "") -> dict[str, Any]:
|
|
|
602
977
|
},
|
|
603
978
|
"notes": [
|
|
604
979
|
"This mode does not modify user-level Claude files.",
|
|
605
|
-
"Use this preview
|
|
980
|
+
"Use this preview only when intentionally inspecting a user-level integration surface.",
|
|
606
981
|
"The preview includes MCP tool allow rules for codecgc, codex, and gemini servers.",
|
|
607
982
|
"Current CodeCGC product policy still defaults to project-local installation.",
|
|
608
983
|
],
|
|
@@ -620,7 +995,7 @@ def install_user_runtime(override_root: str = "") -> dict[str, Any]:
|
|
|
620
995
|
merged_settings, settings_changed = merge_hook_settings(settings, build_user_hook_command(user_paths))
|
|
621
996
|
merged_settings, permissions_changed = merge_permission_settings(merged_settings, DEFAULT_ALLOWED_TOOLS)
|
|
622
997
|
write_json_file(user_paths["settings"], merged_settings)
|
|
623
|
-
write_json_file(user_paths["mcp"], build_mcp_config())
|
|
998
|
+
write_json_file(user_paths["mcp"], build_mcp_config(WORKSPACE))
|
|
624
999
|
shutil.copyfile(PROJECT_HOOK_PATH, user_paths["hook_script"])
|
|
625
1000
|
written_commands = write_custom_command_files(user_paths["commands_dir"], WORKSPACE / "bin")
|
|
626
1001
|
summary = build_mode_summary_payload(
|
|
@@ -681,11 +1056,15 @@ def build_install_mode_summary(result: dict[str, Any]) -> str:
|
|
|
681
1056
|
f"- MCP 配置: {result.get('mcp_config', '')}",
|
|
682
1057
|
f"- Routing 文件: {result.get('routing_file', '')}",
|
|
683
1058
|
f"- Claude 设置: {result.get('claude_settings', '')}",
|
|
1059
|
+
f"- Codex 策略: {result.get('codex_policy', '')}",
|
|
1060
|
+
f"- Gemini 策略: {result.get('gemini_policy', '')}",
|
|
684
1061
|
f"- Hook 脚本: {result.get('hook_script', '')}",
|
|
685
1062
|
f"- Slash Commands: {result.get('commands_dir', '')}",
|
|
1063
|
+
f"- 新手入口: {result.get('onboarding_file', '')}",
|
|
686
1064
|
"- 说明: 可选外部能力如 MemOS 不由 cgc-install 自动写入;如需启用,请在 Claude 中单独配置官方 MCP。",
|
|
687
1065
|
]
|
|
688
1066
|
next_actions = [
|
|
1067
|
+
"cgc-start",
|
|
689
1068
|
"cgc-status",
|
|
690
1069
|
"cgc-doctor",
|
|
691
1070
|
]
|
|
@@ -707,7 +1086,7 @@ def build_install_mode_summary(result: dict[str, Any]) -> str:
|
|
|
707
1086
|
next_actions = []
|
|
708
1087
|
user_root = str(result.get("user_claude_root", "")).strip()
|
|
709
1088
|
if user_root:
|
|
710
|
-
next_actions.append(
|
|
1089
|
+
next_actions.append("cgc-install")
|
|
711
1090
|
next_actions.append("cgc-install --mode status")
|
|
712
1091
|
return render_summary_block("CodeCGC 用户级预演", lines, next_actions)
|
|
713
1092
|
|
|
@@ -734,45 +1113,87 @@ def build_install_mode_summary(result: dict[str, Any]) -> str:
|
|
|
734
1113
|
|
|
735
1114
|
|
|
736
1115
|
def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
|
|
737
|
-
expected_mcp = build_mcp_config()
|
|
1116
|
+
expected_mcp = build_mcp_config(workspace_paths["root"])
|
|
738
1117
|
expected_hook_command = build_workspace_hook_command(workspace_paths)
|
|
739
1118
|
expected_hook_text = load_text_file(PROJECT_HOOK_PATH)
|
|
1119
|
+
expected_settings = build_project_claude_settings(workspace_paths)
|
|
1120
|
+
expected_codex_policy = load_text_file(CODEX_POLICY_TEMPLATE)
|
|
1121
|
+
expected_gemini_policy = load_text_file(GEMINI_POLICY_TEMPLATE)
|
|
740
1122
|
current_settings = load_json_file(workspace_paths["settings"])
|
|
741
1123
|
current_mcp = load_json_file(workspace_paths["mcp"])
|
|
742
1124
|
current_hook_text = load_text_file(workspace_paths["hook_script"])
|
|
1125
|
+
current_codex_policy = load_text_file(workspace_paths["codex_policy"])
|
|
1126
|
+
current_gemini_policy = load_text_file(workspace_paths["gemini_policy"])
|
|
743
1127
|
routing_exists = workspace_paths["routing_file"].exists()
|
|
1128
|
+
policy_valid = policy_file_is_valid(workspace_paths["routing_file"]) if routing_exists else False
|
|
1129
|
+
workflow_dirs_ready = workspace_workflow_dirs_ready(workspace_paths["root"])
|
|
1130
|
+
onboarding_ready = onboarding_file_is_valid(workspace_paths["onboarding_file"])
|
|
744
1131
|
|
|
745
1132
|
hook_registered = settings_have_hook_command(current_settings, expected_hook_command)
|
|
746
1133
|
permissions_registered = settings_have_allowed_tools(current_settings, DEFAULT_ALLOWED_TOOLS)
|
|
1134
|
+
settings_matches = current_settings == expected_settings if workspace_paths["settings"].exists() else False
|
|
747
1135
|
mcp_matches = current_mcp == expected_mcp if workspace_paths["mcp"].exists() else False
|
|
748
1136
|
hook_file_matches = current_hook_text == expected_hook_text if workspace_paths["hook_script"].exists() else False
|
|
1137
|
+
codex_policy_matches = (
|
|
1138
|
+
current_codex_policy == expected_codex_policy if workspace_paths["codex_policy"].exists() else False
|
|
1139
|
+
)
|
|
1140
|
+
gemini_policy_matches = (
|
|
1141
|
+
current_gemini_policy == expected_gemini_policy if workspace_paths["gemini_policy"].exists() else False
|
|
1142
|
+
)
|
|
749
1143
|
|
|
750
1144
|
missing = []
|
|
751
1145
|
if not routing_exists:
|
|
752
1146
|
missing.append("routing_file")
|
|
1147
|
+
if routing_exists and not policy_valid:
|
|
1148
|
+
missing.append("routing_policy")
|
|
1149
|
+
if not workflow_dirs_ready:
|
|
1150
|
+
missing.append("workflow_dirs")
|
|
753
1151
|
if not mcp_matches:
|
|
754
1152
|
missing.append("mcp_json")
|
|
1153
|
+
if not settings_matches:
|
|
1154
|
+
missing.append("claude_settings_local")
|
|
755
1155
|
if not hook_registered:
|
|
756
1156
|
missing.append("claude_settings_hook")
|
|
757
1157
|
if not permissions_registered:
|
|
758
1158
|
missing.append("claude_settings_permissions")
|
|
759
1159
|
if not hook_file_matches:
|
|
760
1160
|
missing.append("hook_script")
|
|
1161
|
+
if not onboarding_ready:
|
|
1162
|
+
missing.append("onboarding_file")
|
|
1163
|
+
if not codex_policy_matches:
|
|
1164
|
+
missing.append("codex_policy")
|
|
1165
|
+
if not gemini_policy_matches:
|
|
1166
|
+
missing.append("gemini_policy")
|
|
761
1167
|
|
|
762
1168
|
ready = not missing
|
|
763
1169
|
return {
|
|
764
1170
|
"mcp_json_path": str(workspace_paths["mcp"]),
|
|
765
1171
|
"routing_file_path": str(workspace_paths["routing_file"]),
|
|
766
1172
|
"claude_settings_path": str(workspace_paths["settings"]),
|
|
1173
|
+
"legacy_claude_settings_path": str(workspace_paths["legacy_settings"]),
|
|
1174
|
+
"codex_policy_path": str(workspace_paths["codex_policy"]),
|
|
1175
|
+
"gemini_policy_path": str(workspace_paths["gemini_policy"]),
|
|
767
1176
|
"hook_script_path": str(workspace_paths["hook_script"]),
|
|
1177
|
+
"onboarding_file_path": str(workspace_paths["onboarding_file"]),
|
|
768
1178
|
"mcp_json_exists": workspace_paths["mcp"].exists(),
|
|
769
1179
|
"routing_file_exists": routing_exists,
|
|
1180
|
+
"routing_policy_valid": policy_valid,
|
|
1181
|
+
"workflow_dirs_ready": workflow_dirs_ready,
|
|
1182
|
+
"onboarding_ready": onboarding_ready,
|
|
1183
|
+
"workflow_dirs_expected": [str(workspace_paths["root"] / item) for item in PROJECT_WORKFLOW_DIRS],
|
|
770
1184
|
"claude_settings_exists": workspace_paths["settings"].exists(),
|
|
1185
|
+
"legacy_claude_settings_exists": workspace_paths["legacy_settings"].exists(),
|
|
1186
|
+
"codex_policy_exists": workspace_paths["codex_policy"].exists(),
|
|
1187
|
+
"gemini_policy_exists": workspace_paths["gemini_policy"].exists(),
|
|
771
1188
|
"hook_exists": workspace_paths["hook_script"].exists(),
|
|
1189
|
+
"onboarding_exists": workspace_paths["onboarding_file"].exists(),
|
|
772
1190
|
"mcp_matches_expected": mcp_matches,
|
|
1191
|
+
"claude_settings_matches_expected": settings_matches,
|
|
773
1192
|
"hook_registered": hook_registered,
|
|
774
1193
|
"permissions_registered": permissions_registered,
|
|
775
1194
|
"hook_file_matches_expected": hook_file_matches,
|
|
1195
|
+
"codex_policy_matches_expected": codex_policy_matches,
|
|
1196
|
+
"gemini_policy_matches_expected": gemini_policy_matches,
|
|
776
1197
|
"ready": ready,
|
|
777
1198
|
"missing_or_outdated": missing,
|
|
778
1199
|
"recommended_command": "" if ready else build_workspace_install_command(workspace_paths["root"]),
|
|
@@ -782,7 +1203,7 @@ def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
782
1203
|
|
|
783
1204
|
|
|
784
1205
|
def collect_user_status(user_paths: dict[str, Path]) -> dict[str, Any]:
|
|
785
|
-
expected_mcp = build_mcp_config()
|
|
1206
|
+
expected_mcp = build_mcp_config(WORKSPACE)
|
|
786
1207
|
expected_hook_command = build_user_hook_command(user_paths)
|
|
787
1208
|
expected_hook_text = load_text_file(PROJECT_HOOK_PATH)
|
|
788
1209
|
current_settings = load_json_file(user_paths["settings"])
|
|
@@ -791,7 +1212,9 @@ def collect_user_status(user_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
791
1212
|
|
|
792
1213
|
hook_registered = settings_have_hook_command(current_settings, expected_hook_command)
|
|
793
1214
|
permissions_registered = settings_have_allowed_tools(current_settings, DEFAULT_ALLOWED_TOOLS)
|
|
794
|
-
|
|
1215
|
+
mcp_matches_exact = current_mcp == expected_mcp if user_paths["mcp"].exists() else False
|
|
1216
|
+
mcp_matches_runtime = mcp_config_matches_runtime_shape(current_mcp) if user_paths["mcp"].exists() else False
|
|
1217
|
+
mcp_matches = mcp_matches_exact or mcp_matches_runtime
|
|
795
1218
|
hook_file_matches = current_hook_text == expected_hook_text if user_paths["hook_script"].exists() else False
|
|
796
1219
|
|
|
797
1220
|
missing = []
|
|
@@ -814,6 +1237,8 @@ def collect_user_status(user_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
814
1237
|
"mcp_exists": user_paths["mcp"].exists(),
|
|
815
1238
|
"hook_exists": user_paths["hook_script"].exists(),
|
|
816
1239
|
"mcp_matches_expected": mcp_matches,
|
|
1240
|
+
"mcp_matches_expected_exact": mcp_matches_exact,
|
|
1241
|
+
"mcp_matches_runtime_shape": mcp_matches_runtime,
|
|
817
1242
|
"hook_registered": hook_registered,
|
|
818
1243
|
"permissions_registered": permissions_registered,
|
|
819
1244
|
"hook_file_matches_expected": hook_file_matches,
|
|
@@ -845,6 +1270,20 @@ def collect_install_status(override_workspace: str = "") -> dict[str, Any]:
|
|
|
845
1270
|
"scope": "项目级集成就绪状态,以及用户级 Claude 集成预演状态",
|
|
846
1271
|
}
|
|
847
1272
|
|
|
1273
|
+
status_summary.update(
|
|
1274
|
+
{
|
|
1275
|
+
"default_policy": "project-local-first",
|
|
1276
|
+
"recommended_next_command": project_status["recommended_command"],
|
|
1277
|
+
"recommended_user_command": "",
|
|
1278
|
+
"human_summary": (
|
|
1279
|
+
"Project-local CodeCGC integration is ready."
|
|
1280
|
+
if project_status["ready"]
|
|
1281
|
+
else "Project-local CodeCGC integration is missing or outdated."
|
|
1282
|
+
),
|
|
1283
|
+
"scope": "project-local integration status; user-level config is optional preview only",
|
|
1284
|
+
}
|
|
1285
|
+
)
|
|
1286
|
+
|
|
848
1287
|
return {
|
|
849
1288
|
"success": True,
|
|
850
1289
|
"mode": "status",
|
|
@@ -856,6 +1295,51 @@ def collect_install_status(override_workspace: str = "") -> dict[str, Any]:
|
|
|
856
1295
|
}
|
|
857
1296
|
|
|
858
1297
|
|
|
1298
|
+
def collect_start_status(override_workspace: str = "") -> dict[str, Any]:
|
|
1299
|
+
workspace_paths = get_workspace_paths(override_workspace)
|
|
1300
|
+
project_status = collect_project_status(workspace_paths)
|
|
1301
|
+
onboarding_path = workspace_paths["onboarding_file"]
|
|
1302
|
+
onboarding_ready = onboarding_file_is_valid(onboarding_path)
|
|
1303
|
+
guide_text = load_text_file(onboarding_path) if onboarding_ready else ""
|
|
1304
|
+
recommended_next_action = "cgc-status" if onboarding_ready else build_workspace_install_command(workspace_paths["root"])
|
|
1305
|
+
human_summary = (
|
|
1306
|
+
"CodeCGC first-run guide is ready. Start with /cgc or cgc after status/doctor are green."
|
|
1307
|
+
if onboarding_ready
|
|
1308
|
+
else "CodeCGC first-run guide is missing or outdated. Run project-local install first."
|
|
1309
|
+
)
|
|
1310
|
+
quick_actions = [
|
|
1311
|
+
"/cgc-status",
|
|
1312
|
+
"/cgc-doctor",
|
|
1313
|
+
"/cgc <你的需求>",
|
|
1314
|
+
"cgc-status",
|
|
1315
|
+
"cgc-doctor",
|
|
1316
|
+
"cgc \"你的需求\"",
|
|
1317
|
+
]
|
|
1318
|
+
if not onboarding_ready:
|
|
1319
|
+
quick_actions = ["/cgc-install", build_workspace_install_command(workspace_paths["root"])]
|
|
1320
|
+
|
|
1321
|
+
return {
|
|
1322
|
+
"success": True,
|
|
1323
|
+
"mode": "start",
|
|
1324
|
+
"workspace": str(workspace_paths["root"]),
|
|
1325
|
+
"summary": {
|
|
1326
|
+
"ready": onboarding_ready,
|
|
1327
|
+
"human_summary": human_summary,
|
|
1328
|
+
"scope": "project-local first-run guide and next actions",
|
|
1329
|
+
"recommended_next_action": recommended_next_action,
|
|
1330
|
+
"quick_actions": quick_actions,
|
|
1331
|
+
},
|
|
1332
|
+
"onboarding": {
|
|
1333
|
+
"path": str(onboarding_path),
|
|
1334
|
+
"exists": onboarding_path.exists(),
|
|
1335
|
+
"ready": onboarding_ready,
|
|
1336
|
+
"relative_path": PROJECT_ONBOARDING_RELATIVE_PATH,
|
|
1337
|
+
"guide_excerpt": guide_text[:1200],
|
|
1338
|
+
},
|
|
1339
|
+
"project": project_status,
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
|
|
859
1343
|
def find_python_command() -> str:
|
|
860
1344
|
candidates = ["python", "py"] if os.name == "nt" else ["python3", "python"]
|
|
861
1345
|
for candidate in candidates:
|
|
@@ -1065,11 +1549,20 @@ def classify_doctor_failures(
|
|
|
1065
1549
|
)
|
|
1066
1550
|
continue
|
|
1067
1551
|
|
|
1068
|
-
if name
|
|
1552
|
+
if name == "onboarding_file_ready":
|
|
1553
|
+
add_failure(
|
|
1554
|
+
"onboarding-guide-missing",
|
|
1555
|
+
"项目级新手入口文件缺失或已过期。",
|
|
1556
|
+
"重新执行项目级安装以同步 `codecgc/START_HERE.md` 与 `/cgc-start` 入口。",
|
|
1557
|
+
install_command,
|
|
1558
|
+
)
|
|
1559
|
+
continue
|
|
1560
|
+
|
|
1561
|
+
if name in {"routing_file_exists", "routing_policy_valid", "project_hook_source_exists"}:
|
|
1069
1562
|
add_failure(
|
|
1070
1563
|
"packaged-runtime-missing-files",
|
|
1071
|
-
"
|
|
1072
|
-
"
|
|
1564
|
+
"运行时所需的 policy、路由文件或 hook 源文件缺失/无效。",
|
|
1565
|
+
"重新执行项目级安装以同步 model-routing.yaml、policy-backed hook 与 Claude settings。",
|
|
1073
1566
|
)
|
|
1074
1567
|
continue
|
|
1075
1568
|
|
|
@@ -1104,6 +1597,21 @@ def collect_doctor_status(override_workspace: str = "") -> dict[str, Any]:
|
|
|
1104
1597
|
"ok": workspace_paths["routing_file"].exists(),
|
|
1105
1598
|
"detail": str(workspace_paths["routing_file"]),
|
|
1106
1599
|
},
|
|
1600
|
+
{
|
|
1601
|
+
"name": "routing_policy_valid",
|
|
1602
|
+
"ok": policy_file_is_valid(workspace_paths["routing_file"]) if workspace_paths["routing_file"].exists() else False,
|
|
1603
|
+
"detail": str(workspace_paths["routing_file"]),
|
|
1604
|
+
},
|
|
1605
|
+
{
|
|
1606
|
+
"name": "workflow_dirs_ready",
|
|
1607
|
+
"ok": workspace_workflow_dirs_ready(workspace_paths["root"]),
|
|
1608
|
+
"detail": str(workspace_paths["root"] / "codecgc"),
|
|
1609
|
+
},
|
|
1610
|
+
{
|
|
1611
|
+
"name": "onboarding_file_ready",
|
|
1612
|
+
"ok": onboarding_file_is_valid(workspace_paths["onboarding_file"]),
|
|
1613
|
+
"detail": str(workspace_paths["onboarding_file"]),
|
|
1614
|
+
},
|
|
1107
1615
|
{
|
|
1108
1616
|
"name": "project_hook_source_exists",
|
|
1109
1617
|
"ok": PROJECT_HOOK_PATH.exists(),
|
|
@@ -1204,7 +1712,7 @@ def collect_doctor_status(override_workspace: str = "") -> dict[str, Any]:
|
|
|
1204
1712
|
|
|
1205
1713
|
def build_parser() -> argparse.ArgumentParser:
|
|
1206
1714
|
parser = argparse.ArgumentParser(description="Install or inspect CodeCGC integration surfaces.")
|
|
1207
|
-
parser.add_argument("--mode", choices=["local", "user-dry-run", "user", "status", "doctor"], default="local")
|
|
1715
|
+
parser.add_argument("--mode", choices=["local", "user-dry-run", "user", "status", "doctor", "start"], default="local")
|
|
1208
1716
|
parser.add_argument(
|
|
1209
1717
|
"--format",
|
|
1210
1718
|
choices=["json", "summary"],
|
|
@@ -1234,9 +1742,28 @@ def main() -> int:
|
|
|
1234
1742
|
result = collect_install_status(args.workspace)
|
|
1235
1743
|
elif args.mode == "doctor":
|
|
1236
1744
|
result = collect_doctor_status(args.workspace)
|
|
1745
|
+
elif args.mode == "start":
|
|
1746
|
+
result = collect_start_status(args.workspace)
|
|
1237
1747
|
else:
|
|
1238
1748
|
raise ValueError(f"Unsupported install mode: {args.mode}")
|
|
1239
1749
|
|
|
1750
|
+
if args.mode == "start" and args.format == "summary":
|
|
1751
|
+
summary = result.get("summary", {}) if isinstance(result.get("summary"), dict) else {}
|
|
1752
|
+
onboarding = result.get("onboarding", {}) if isinstance(result.get("onboarding"), dict) else {}
|
|
1753
|
+
quick_actions = summary.get("quick_actions", []) if isinstance(summary.get("quick_actions"), list) else []
|
|
1754
|
+
lines = [
|
|
1755
|
+
f"- 工作区: {result.get('workspace', '')}",
|
|
1756
|
+
f"- 范围: {summary.get('scope', '')}",
|
|
1757
|
+
f"- 新手入口就绪: {format_bool_zh(summary.get('ready'))}",
|
|
1758
|
+
f"- 入口文件: {onboarding.get('path', '')}",
|
|
1759
|
+
f"- 摘要: {summary.get('human_summary', '')}",
|
|
1760
|
+
f"- 快速动作: {format_list_zh(quick_actions)}",
|
|
1761
|
+
]
|
|
1762
|
+
next_action = str(summary.get("recommended_next_action", "")).strip()
|
|
1763
|
+
next_actions = [next_action] if next_action else []
|
|
1764
|
+
print(render_summary_block("CodeCGC Start", lines, next_actions))
|
|
1765
|
+
return 0 if result.get("success") else 1
|
|
1766
|
+
|
|
1240
1767
|
if args.mode == "status" and args.format == "summary":
|
|
1241
1768
|
summary = result.get("summary", {}) if isinstance(result.get("summary"), dict) else {}
|
|
1242
1769
|
project = result.get("project", {}) if isinstance(result.get("project"), dict) else {}
|
|
@@ -1249,6 +1776,7 @@ def main() -> int:
|
|
|
1249
1776
|
f"- 策略: {summary.get('default_policy', '')}",
|
|
1250
1777
|
f"- 摘要: {summary.get('human_summary', '')}",
|
|
1251
1778
|
f"- 项目级缺失项: {format_list_zh(project.get('missing_or_outdated', []))}",
|
|
1779
|
+
f"- 新手入口: {project.get('onboarding_file_path', '')}",
|
|
1252
1780
|
f"- 用户级缺失项: {format_list_zh(user.get('missing_or_outdated', []))}",
|
|
1253
1781
|
]
|
|
1254
1782
|
recommended_project = str(summary.get("recommended_project_command", "")).strip()
|