@hunyed15/codecgc 0.1.6 → 0.1.8
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 +117 -484
- package/README.md +118 -150
- 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/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 +57 -0
- package/codecgc/reference/project-structure.md +80 -0
- package/codecgc/reference/quickstart.md +108 -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 +112 -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/codecgcmcp/README.md +57 -11
- package/codecgcmcp/src/codecgcmcp/server.py +164 -26
- package/codexmcp/src/codexmcp/server.py +32 -26
- package/geminimcp/src/geminimcp/server.py +22 -14
- package/model-routing.yaml +31 -6
- package/package.json +11 -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 +73 -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 +62 -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 +275 -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 +169 -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 +490 -21
- 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,16 @@ 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
|
+
EDIT_GUARDRAIL_MATCHER = "Edit|Write|MultiEdit"
|
|
29
|
+
LEGACY_EDIT_GUARDRAIL_MATCHERS = {"Edit|Write"}
|
|
30
|
+
PROJECT_ONBOARDING_RELATIVE_PATH = "codecgc/START_HERE.md"
|
|
31
|
+
PROJECT_ONBOARDING_MARKER = "<!-- codecgc:onboarding:v1 -->"
|
|
27
32
|
|
|
28
33
|
|
|
29
34
|
DEFAULT_HOOKS = {
|
|
30
35
|
"PreToolUse": [
|
|
31
36
|
{
|
|
32
|
-
"matcher":
|
|
37
|
+
"matcher": EDIT_GUARDRAIL_MATCHER,
|
|
33
38
|
"hooks": [
|
|
34
39
|
{
|
|
35
40
|
"type": "command",
|
|
@@ -48,6 +53,22 @@ DEFAULT_ALLOWED_TOOLS = [
|
|
|
48
53
|
"mcp__gemini__*",
|
|
49
54
|
]
|
|
50
55
|
|
|
56
|
+
PROJECT_WORKFLOW_DIRS = [
|
|
57
|
+
"codecgc/features",
|
|
58
|
+
"codecgc/issues",
|
|
59
|
+
"codecgc/execution",
|
|
60
|
+
"codecgc/requirements",
|
|
61
|
+
"codecgc/architecture",
|
|
62
|
+
"codecgc/roadmap",
|
|
63
|
+
"codecgc/compound",
|
|
64
|
+
"codecgc/docs",
|
|
65
|
+
"codecgc/reference",
|
|
66
|
+
"codecgc/fixtures/features",
|
|
67
|
+
"codecgc/fixtures/issues",
|
|
68
|
+
"codecgc/fixtures/execution",
|
|
69
|
+
"codecgc/fixtures/roadmap",
|
|
70
|
+
]
|
|
71
|
+
|
|
51
72
|
|
|
52
73
|
def get_user_claude_root(override_root: str = "") -> Path:
|
|
53
74
|
override = override_root.strip() or os.environ.get("CODECGC_USER_CLAUDE_DIR", "").strip()
|
|
@@ -86,6 +107,7 @@ def get_workspace_paths(override_workspace: str = "") -> dict[str, Path]:
|
|
|
86
107
|
"hook_script": hooks_dir / "route-edit.ps1",
|
|
87
108
|
"commands_dir": claude_dir / "commands",
|
|
88
109
|
"routing_file": root / "model-routing.yaml",
|
|
110
|
+
"onboarding_file": root / PROJECT_ONBOARDING_RELATIVE_PATH,
|
|
89
111
|
}
|
|
90
112
|
|
|
91
113
|
|
|
@@ -101,11 +123,122 @@ def load_text_file(path: Path) -> str:
|
|
|
101
123
|
return path.read_text(encoding="utf-8")
|
|
102
124
|
|
|
103
125
|
|
|
126
|
+
def ensure_workspace_workflow_dirs(workspace_root: Path) -> list[str]:
|
|
127
|
+
created_or_existing: list[str] = []
|
|
128
|
+
for relative_path in PROJECT_WORKFLOW_DIRS:
|
|
129
|
+
directory = workspace_root / relative_path
|
|
130
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
131
|
+
created_or_existing.append(str(directory))
|
|
132
|
+
return created_or_existing
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def workspace_workflow_dirs_ready(workspace_root: Path) -> bool:
|
|
136
|
+
return all((workspace_root / relative_path).is_dir() for relative_path in PROJECT_WORKFLOW_DIRS)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def build_project_onboarding_text() -> str:
|
|
140
|
+
return f"""{PROJECT_ONBOARDING_MARKER}
|
|
141
|
+
# CodeCGC Start Here
|
|
142
|
+
|
|
143
|
+
This file is generated by `cgc-install` for this project. It is intentionally project-relative and should not contain machine-specific install paths.
|
|
144
|
+
|
|
145
|
+
## First Run
|
|
146
|
+
|
|
147
|
+
Inside Claude:
|
|
148
|
+
|
|
149
|
+
```text
|
|
150
|
+
/cgc-start
|
|
151
|
+
/cgc-status
|
|
152
|
+
/cgc-doctor
|
|
153
|
+
/cgc 新增一个登录页面,放在 src/components/LoginForm.tsx
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Outside Claude:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
cgc-start
|
|
160
|
+
cgc-status
|
|
161
|
+
cgc-doctor
|
|
162
|
+
cgc "新增一个登录页面,放在 src/components/LoginForm.tsx"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## What CodeCGC Owns
|
|
166
|
+
|
|
167
|
+
- Claude owns orchestration, requirements, design notes, docs, review, acceptance, and workflow state.
|
|
168
|
+
- Codex owns backend implementation and backend tests.
|
|
169
|
+
- Gemini owns frontend implementation and frontend tests.
|
|
170
|
+
- Mixed backend/frontend work should be split before execution.
|
|
171
|
+
|
|
172
|
+
## Installed Project Surface
|
|
173
|
+
|
|
174
|
+
```text
|
|
175
|
+
.mcp.json
|
|
176
|
+
model-routing.yaml
|
|
177
|
+
.claude/settings.json
|
|
178
|
+
.claude/hooks/route-edit.ps1
|
|
179
|
+
.claude/commands/cgc*.md
|
|
180
|
+
codecgc/features/
|
|
181
|
+
codecgc/issues/
|
|
182
|
+
codecgc/execution/
|
|
183
|
+
codecgc/requirements/
|
|
184
|
+
codecgc/architecture/
|
|
185
|
+
codecgc/roadmap/
|
|
186
|
+
codecgc/compound/
|
|
187
|
+
codecgc/docs/
|
|
188
|
+
codecgc/reference/
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Normal Loop
|
|
192
|
+
|
|
193
|
+
1. Start with `/cgc <your request>` or `cgc "<your request>"`.
|
|
194
|
+
2. Let CodeCGC decide whether the next step is planning, execution, review, or closure.
|
|
195
|
+
3. Review execution audits with `/cgc-review` or `cgc-review`.
|
|
196
|
+
4. Use `/cgc-history` or `cgc-history` when you need to find open work.
|
|
197
|
+
|
|
198
|
+
## Recovery
|
|
199
|
+
|
|
200
|
+
- If project integration looks wrong, run `cgc-install`, then `cgc-status`.
|
|
201
|
+
- If runtime or executor startup fails, run `cgc-doctor`.
|
|
202
|
+
- If a write is blocked, inspect `model-routing.yaml`; it is the project-local policy source of truth.
|
|
203
|
+
- If an execution was rejected, use `/cgc` or `cgc` to continue the same workflow rather than starting a new one.
|
|
204
|
+
|
|
205
|
+
## Stable References
|
|
206
|
+
|
|
207
|
+
- `codecgc/reference/quickstart.md`
|
|
208
|
+
- `codecgc/reference/onboarding.md`
|
|
209
|
+
- `codecgc/reference/operation-guide.md`
|
|
210
|
+
- `codecgc/reference/troubleshooting.md`
|
|
211
|
+
- `codecgc/reference/path-contract.md`
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def write_project_onboarding_file(workspace_root: Path) -> Path:
|
|
216
|
+
path = workspace_root / PROJECT_ONBOARDING_RELATIVE_PATH
|
|
217
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
218
|
+
path.write_text(build_project_onboarding_text(), encoding="utf-8")
|
|
219
|
+
return path
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def onboarding_file_is_valid(path: Path) -> bool:
|
|
223
|
+
text = load_text_file(path)
|
|
224
|
+
if not text:
|
|
225
|
+
return False
|
|
226
|
+
required_markers = [
|
|
227
|
+
PROJECT_ONBOARDING_MARKER,
|
|
228
|
+
"/cgc-start",
|
|
229
|
+
"cgc-start",
|
|
230
|
+
"/cgc-status",
|
|
231
|
+
"cgc-status",
|
|
232
|
+
"model-routing.yaml",
|
|
233
|
+
]
|
|
234
|
+
return all(marker in text for marker in required_markers)
|
|
235
|
+
|
|
236
|
+
|
|
104
237
|
def build_hook_payload(command_text: str) -> dict[str, Any]:
|
|
105
238
|
return {
|
|
106
239
|
"PreToolUse": [
|
|
107
240
|
{
|
|
108
|
-
"matcher":
|
|
241
|
+
"matcher": EDIT_GUARDRAIL_MATCHER,
|
|
109
242
|
"hooks": [
|
|
110
243
|
{
|
|
111
244
|
"type": "command",
|
|
@@ -117,6 +250,14 @@ def build_hook_payload(command_text: str) -> dict[str, Any]:
|
|
|
117
250
|
}
|
|
118
251
|
|
|
119
252
|
|
|
253
|
+
def policy_file_is_valid(path: Path) -> bool:
|
|
254
|
+
try:
|
|
255
|
+
load_policy(path)
|
|
256
|
+
except Exception:
|
|
257
|
+
return False
|
|
258
|
+
return True
|
|
259
|
+
|
|
260
|
+
|
|
120
261
|
def _normalize_command_path_for_markdown(path: Path) -> str:
|
|
121
262
|
return str(path).replace("\\", "\\\\")
|
|
122
263
|
|
|
@@ -323,6 +464,17 @@ def build_custom_command_templates(bin_dir: Path) -> dict[str, str]:
|
|
|
323
464
|
],
|
|
324
465
|
fallback_command="cgc-external-audit",
|
|
325
466
|
),
|
|
467
|
+
build_mcp_first_command_template(
|
|
468
|
+
filename="cgc-external-status.md",
|
|
469
|
+
description="查看外部 MCP 能力状态面板",
|
|
470
|
+
argument_hint="[参数]",
|
|
471
|
+
primary_tool="codecgc.external_status",
|
|
472
|
+
direct_rules=[
|
|
473
|
+
"映射可选字段 `workspace` 和 `format`。",
|
|
474
|
+
"该命令用于日常快速查看外部能力登记状态与本地 MCP 观测结果。",
|
|
475
|
+
],
|
|
476
|
+
fallback_command="cgc-external-status",
|
|
477
|
+
),
|
|
326
478
|
build_mcp_first_command_template(
|
|
327
479
|
filename="cgc-release-readiness.md",
|
|
328
480
|
description="运行 CodeCGC 发布就绪检查",
|
|
@@ -347,6 +499,57 @@ def build_custom_command_templates(bin_dir: Path) -> dict[str, str]:
|
|
|
347
499
|
),
|
|
348
500
|
]
|
|
349
501
|
)
|
|
502
|
+
templates["cgc-install.md"] = """---
|
|
503
|
+
description: Install or repair CodeCGC project-local integration
|
|
504
|
+
argument-hint: "[optional install arguments]"
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
Use the `codecgc.install` MCP tool as the primary path.
|
|
508
|
+
|
|
509
|
+
Default behavior:
|
|
510
|
+
|
|
511
|
+
- Run project-local install for the current workspace.
|
|
512
|
+
- Do not install user-level Claude files unless the user explicitly asks for `--mode user`.
|
|
513
|
+
- Treat `model-routing.yaml` as the single routing policy source.
|
|
514
|
+
|
|
515
|
+
Argument mapping:
|
|
516
|
+
|
|
517
|
+
- No arguments: call `codecgc.install` with `mode: "local"`.
|
|
518
|
+
- `status`: call `codecgc.install` with `mode: "status"`.
|
|
519
|
+
- `doctor`: call `codecgc.install` with `mode: "doctor"`.
|
|
520
|
+
- `--workspace <dir>`: pass the workspace through.
|
|
521
|
+
- `--mode user-dry-run`: preview user-level files only.
|
|
522
|
+
- `--mode user`: only run if the user explicitly asks to write user-level Claude integration.
|
|
523
|
+
|
|
524
|
+
Fallback:
|
|
525
|
+
|
|
526
|
+
- If the MCP tool is unavailable, run `cgc-install` with the same arguments from the target project root.
|
|
527
|
+
- After install, run or suggest `cgc-start`, `cgc-status`, and `cgc-doctor`.
|
|
528
|
+
"""
|
|
529
|
+
templates["cgc-start.md"] = """---
|
|
530
|
+
description: Show the CodeCGC first-run entry guide for this project
|
|
531
|
+
argument-hint: "[optional workspace]"
|
|
532
|
+
---
|
|
533
|
+
|
|
534
|
+
Use `codecgc.start` as the primary path.
|
|
535
|
+
|
|
536
|
+
Default behavior:
|
|
537
|
+
|
|
538
|
+
- Treat this as a read-only onboarding/status entry.
|
|
539
|
+
- Summarize the project-local `codecgc/START_HERE.md` guide if present.
|
|
540
|
+
- If the guide is missing, tell the user to run `/cgc-install`.
|
|
541
|
+
- Keep the answer short and in Chinese.
|
|
542
|
+
|
|
543
|
+
Argument mapping:
|
|
544
|
+
|
|
545
|
+
- No arguments: call `codecgc.start`.
|
|
546
|
+
- `--workspace <dir>`: pass the workspace through.
|
|
547
|
+
|
|
548
|
+
Fallback:
|
|
549
|
+
|
|
550
|
+
- If the MCP tool is unavailable, run `cgc-start` from the target project root.
|
|
551
|
+
- If `cgc-start` reports missing onboarding, suggest `cgc-install`.
|
|
552
|
+
"""
|
|
350
553
|
return templates
|
|
351
554
|
|
|
352
555
|
|
|
@@ -361,6 +564,15 @@ def write_custom_command_files(target_dir: Path, bin_dir: Path) -> list[str]:
|
|
|
361
564
|
return written
|
|
362
565
|
|
|
363
566
|
|
|
567
|
+
def is_codecgc_route_edit_hook(hook: Any) -> bool:
|
|
568
|
+
if not isinstance(hook, dict):
|
|
569
|
+
return False
|
|
570
|
+
if hook.get("type") != "command":
|
|
571
|
+
return False
|
|
572
|
+
command = str(hook.get("command", "")).replace("\\", "/").lower()
|
|
573
|
+
return "route-edit.ps1" in command
|
|
574
|
+
|
|
575
|
+
|
|
364
576
|
def merge_hook_settings(current: dict[str, Any], command_text: str) -> tuple[dict[str, Any], bool]:
|
|
365
577
|
hooks = current.get("hooks")
|
|
366
578
|
expected_hooks = build_hook_payload(command_text)
|
|
@@ -373,6 +585,23 @@ def merge_hook_settings(current: dict[str, Any], command_text: str) -> tuple[dic
|
|
|
373
585
|
hooks["PreToolUse"] = expected_hooks["PreToolUse"]
|
|
374
586
|
return current, True
|
|
375
587
|
|
|
588
|
+
changed = False
|
|
589
|
+
for item in list(pre_tool_use):
|
|
590
|
+
if not isinstance(item, dict):
|
|
591
|
+
continue
|
|
592
|
+
if item.get("matcher") not in LEGACY_EDIT_GUARDRAIL_MATCHERS | {EDIT_GUARDRAIL_MATCHER}:
|
|
593
|
+
continue
|
|
594
|
+
hook_list = item.get("hooks")
|
|
595
|
+
if not isinstance(hook_list, list):
|
|
596
|
+
continue
|
|
597
|
+
filtered_hooks = [hook for hook in hook_list if not is_codecgc_route_edit_hook(hook)]
|
|
598
|
+
if len(filtered_hooks) != len(hook_list):
|
|
599
|
+
changed = True
|
|
600
|
+
if filtered_hooks:
|
|
601
|
+
item["hooks"] = filtered_hooks
|
|
602
|
+
else:
|
|
603
|
+
pre_tool_use.remove(item)
|
|
604
|
+
|
|
376
605
|
expected = expected_hooks["PreToolUse"][0]
|
|
377
606
|
for item in pre_tool_use:
|
|
378
607
|
if not isinstance(item, dict):
|
|
@@ -387,7 +616,7 @@ def merge_hook_settings(current: dict[str, Any], command_text: str) -> tuple[dic
|
|
|
387
616
|
if not isinstance(hook, dict):
|
|
388
617
|
continue
|
|
389
618
|
if hook.get("type") == "command" and hook.get("command") == expected["hooks"][0]["command"]:
|
|
390
|
-
return current,
|
|
619
|
+
return current, changed
|
|
391
620
|
hook_list.append(expected["hooks"][0])
|
|
392
621
|
return current, True
|
|
393
622
|
|
|
@@ -466,7 +695,7 @@ def settings_have_hook_command(settings: dict[str, Any], command_text: str) -> b
|
|
|
466
695
|
for item in pre_tool_use:
|
|
467
696
|
if not isinstance(item, dict):
|
|
468
697
|
continue
|
|
469
|
-
if item.get("matcher") !=
|
|
698
|
+
if item.get("matcher") != EDIT_GUARDRAIL_MATCHER:
|
|
470
699
|
continue
|
|
471
700
|
hook_list = item.get("hooks")
|
|
472
701
|
if not isinstance(hook_list, list):
|
|
@@ -490,8 +719,110 @@ def settings_have_allowed_tools(settings: dict[str, Any], allow_rules: list[str]
|
|
|
490
719
|
return all(str(rule).strip() in existing for rule in allow_rules)
|
|
491
720
|
|
|
492
721
|
|
|
722
|
+
RUNTIME_MCP_SERVER_SPECS: dict[str, dict[str, Any]] = {
|
|
723
|
+
"codecgc": {
|
|
724
|
+
"module": "codecgcmcp.cli",
|
|
725
|
+
"pythonpath_suffixes": ["scripts", "codecgcmcp/src"],
|
|
726
|
+
},
|
|
727
|
+
"codex": {
|
|
728
|
+
"module": "codexmcp.cli",
|
|
729
|
+
"pythonpath_suffixes": ["scripts", "codecgcmcp/src", "codexmcp/src"],
|
|
730
|
+
},
|
|
731
|
+
"gemini": {
|
|
732
|
+
"module": "geminimcp.cli",
|
|
733
|
+
"pythonpath_suffixes": ["scripts", "codecgcmcp/src", "geminimcp/src"],
|
|
734
|
+
},
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def _normalize_path_parts(path_text: str) -> tuple[str, ...]:
|
|
739
|
+
text = str(path_text).replace("\\", "/").strip().rstrip("/")
|
|
740
|
+
if not text:
|
|
741
|
+
return ()
|
|
742
|
+
return tuple(part.lower() for part in text.split("/") if part)
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
def _match_runtime_root(path_text: str, suffix: str) -> tuple[str, ...] | None:
|
|
746
|
+
parts = _normalize_path_parts(path_text)
|
|
747
|
+
suffix_parts = _normalize_path_parts(suffix)
|
|
748
|
+
if not parts or not suffix_parts or len(parts) < len(suffix_parts):
|
|
749
|
+
return None
|
|
750
|
+
if parts[-len(suffix_parts) :] != suffix_parts:
|
|
751
|
+
return None
|
|
752
|
+
return parts[: -len(suffix_parts)]
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def _collect_runtime_roots(entries: list[str], suffix: str) -> set[tuple[str, ...]]:
|
|
756
|
+
roots: set[tuple[str, ...]] = set()
|
|
757
|
+
for entry in entries:
|
|
758
|
+
root = _match_runtime_root(entry, suffix)
|
|
759
|
+
if root is not None:
|
|
760
|
+
roots.add(root)
|
|
761
|
+
return roots
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
def mcp_server_matches_runtime_shape(server_payload: dict[str, Any], spec: dict[str, Any]) -> bool:
|
|
765
|
+
command_text = str(server_payload.get("command", "")).strip()
|
|
766
|
+
if not command_text:
|
|
767
|
+
return False
|
|
768
|
+
|
|
769
|
+
args = server_payload.get("args")
|
|
770
|
+
if args != ["-m", spec["module"]]:
|
|
771
|
+
return False
|
|
772
|
+
|
|
773
|
+
env = server_payload.get("env")
|
|
774
|
+
if not isinstance(env, dict):
|
|
775
|
+
return False
|
|
776
|
+
|
|
777
|
+
workspace_text = str(env.get("CODECGC_WORKSPACE_ROOT", "")).strip()
|
|
778
|
+
if not workspace_text:
|
|
779
|
+
return False
|
|
780
|
+
|
|
781
|
+
pythonpath_text = str(env.get("PYTHONPATH", "")).strip()
|
|
782
|
+
if not pythonpath_text:
|
|
783
|
+
return False
|
|
784
|
+
|
|
785
|
+
entries = [item.strip() for item in pythonpath_text.split(os.pathsep) if item.strip()]
|
|
786
|
+
if not entries:
|
|
787
|
+
return False
|
|
788
|
+
|
|
789
|
+
candidate_roots: set[tuple[str, ...]] | None = None
|
|
790
|
+
for suffix in spec["pythonpath_suffixes"]:
|
|
791
|
+
matching_roots = _collect_runtime_roots(entries, str(suffix))
|
|
792
|
+
if not matching_roots:
|
|
793
|
+
return False
|
|
794
|
+
candidate_roots = matching_roots if candidate_roots is None else candidate_roots & matching_roots
|
|
795
|
+
if not candidate_roots:
|
|
796
|
+
return False
|
|
797
|
+
return True
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
def mcp_config_matches_runtime_shape(payload: dict[str, Any]) -> bool:
|
|
801
|
+
servers = payload.get("mcpServers")
|
|
802
|
+
if not isinstance(servers, dict):
|
|
803
|
+
return False
|
|
804
|
+
|
|
805
|
+
for server_name, spec in RUNTIME_MCP_SERVER_SPECS.items():
|
|
806
|
+
server_payload = servers.get(server_name)
|
|
807
|
+
if not isinstance(server_payload, dict):
|
|
808
|
+
return False
|
|
809
|
+
if not mcp_server_matches_runtime_shape(server_payload, spec):
|
|
810
|
+
return False
|
|
811
|
+
return True
|
|
812
|
+
|
|
813
|
+
|
|
493
814
|
def build_workspace_hook_command(workspace_paths: dict[str, Path]) -> str:
|
|
494
|
-
|
|
815
|
+
package_root = str(WORKSPACE).replace("'", "''")
|
|
816
|
+
workspace_root_path = Path(workspace_paths["root"])
|
|
817
|
+
hook_script_path = workspace_paths.get("hook_script", workspace_root_path / ".claude" / "hooks" / "route-edit.ps1")
|
|
818
|
+
workspace_root = str(workspace_root_path).replace("'", "''")
|
|
819
|
+
hook_script = str(hook_script_path).replace("\\", "/").replace("'", "''")
|
|
820
|
+
return (
|
|
821
|
+
"powershell -ExecutionPolicy Bypass -Command "
|
|
822
|
+
f"\"$env:CODECGC_PACKAGE_ROOT='{package_root}'; "
|
|
823
|
+
f"$env:CODECGC_WORKSPACE_ROOT='{workspace_root}'; "
|
|
824
|
+
f"& '{hook_script}'\""
|
|
825
|
+
)
|
|
495
826
|
|
|
496
827
|
|
|
497
828
|
def build_mode_summary_payload(
|
|
@@ -513,11 +844,12 @@ def build_mode_summary_payload(
|
|
|
513
844
|
|
|
514
845
|
def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
|
|
515
846
|
workspace_paths = get_workspace_paths(override_workspace)
|
|
516
|
-
mcp_path = write_mcp_config(workspace_paths["mcp"])
|
|
847
|
+
mcp_path = write_mcp_config(workspace_paths["mcp"], workspace_paths["root"])
|
|
517
848
|
routing_path = sync_workspace_routing_file(workspace_paths["routing_file"])
|
|
518
849
|
|
|
519
850
|
workspace_paths["claude_dir"].mkdir(parents=True, exist_ok=True)
|
|
520
851
|
workspace_paths["hooks_dir"].mkdir(parents=True, exist_ok=True)
|
|
852
|
+
workflow_dirs = ensure_workspace_workflow_dirs(workspace_paths["root"])
|
|
521
853
|
|
|
522
854
|
settings = load_json_file(workspace_paths["settings"])
|
|
523
855
|
merged_settings, settings_changed = merge_hook_settings(
|
|
@@ -533,11 +865,12 @@ def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
|
|
|
533
865
|
if PROJECT_HOOK_PATH.resolve() != workspace_paths["hook_script"].resolve():
|
|
534
866
|
shutil.copyfile(PROJECT_HOOK_PATH, workspace_paths["hook_script"])
|
|
535
867
|
written_commands = write_custom_command_files(workspace_paths["commands_dir"], WORKSPACE / "bin")
|
|
868
|
+
onboarding_file = write_project_onboarding_file(workspace_paths["root"])
|
|
536
869
|
|
|
537
870
|
summary = build_mode_summary_payload(
|
|
538
871
|
scope="项目级 Claude 与 MCP 集成面",
|
|
539
872
|
human_summary="项目级 CodeCGC 集成文件已同步。",
|
|
540
|
-
recommended_next_action="cgc-
|
|
873
|
+
recommended_next_action="cgc-start",
|
|
541
874
|
)
|
|
542
875
|
|
|
543
876
|
return {
|
|
@@ -549,13 +882,17 @@ def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
|
|
|
549
882
|
"claude_settings": str(workspace_paths["settings"]),
|
|
550
883
|
"hook_script": str(workspace_paths["hook_script"]),
|
|
551
884
|
"commands_dir": str(workspace_paths["commands_dir"]),
|
|
885
|
+
"onboarding_file": str(onboarding_file),
|
|
886
|
+
"workflow_dirs": workflow_dirs,
|
|
552
887
|
"command_files": written_commands,
|
|
553
888
|
"notes": [
|
|
554
889
|
"Repository-local MCP config was synced from the executor registry.",
|
|
555
|
-
"Project-local model-routing.yaml was synchronized
|
|
890
|
+
"Project-local model-routing.yaml was synchronized as the policy source of truth.",
|
|
891
|
+
"Project-local codecgc workflow directories were initialized.",
|
|
556
892
|
"Claude pre-edit guardrail hook was synchronized into the target workspace.",
|
|
557
893
|
"Claude MCP tool permissions were merged into project .claude/settings.json.",
|
|
558
894
|
"Project-local Claude slash commands were synchronized into .claude/commands.",
|
|
895
|
+
"Project-local codecgc/START_HERE.md was written as the first-run entry guide.",
|
|
559
896
|
"This mode prepares project-level integration surfaces for the selected workspace.",
|
|
560
897
|
],
|
|
561
898
|
"summary": summary,
|
|
@@ -563,7 +900,15 @@ def install_local_runtime(override_workspace: str = "") -> dict[str, Any]:
|
|
|
563
900
|
|
|
564
901
|
|
|
565
902
|
def build_user_hook_command(user_paths: dict[str, Path]) -> str:
|
|
566
|
-
|
|
903
|
+
package_root = str(WORKSPACE).replace("'", "''")
|
|
904
|
+
workspace_root = str(WORKSPACE).replace("'", "''")
|
|
905
|
+
hook_script = str(user_paths["hook_script"]).replace("'", "''")
|
|
906
|
+
return (
|
|
907
|
+
"powershell -ExecutionPolicy Bypass -Command "
|
|
908
|
+
f"\"$env:CODECGC_PACKAGE_ROOT='{package_root}'; "
|
|
909
|
+
f"$env:CODECGC_WORKSPACE_ROOT='{workspace_root}'; "
|
|
910
|
+
f"& '{hook_script}'\""
|
|
911
|
+
)
|
|
567
912
|
|
|
568
913
|
|
|
569
914
|
def preview_user_install(override_root: str = "") -> dict[str, Any]:
|
|
@@ -571,8 +916,8 @@ def preview_user_install(override_root: str = "") -> dict[str, Any]:
|
|
|
571
916
|
user_settings = load_json_file(user_paths["settings"])
|
|
572
917
|
merged_settings, settings_changed = merge_hook_settings(user_settings, build_user_hook_command(user_paths))
|
|
573
918
|
merged_settings, permissions_changed = merge_permission_settings(merged_settings, DEFAULT_ALLOWED_TOOLS)
|
|
574
|
-
mcp_config = build_mcp_config()
|
|
575
|
-
recommended_next_action =
|
|
919
|
+
mcp_config = build_mcp_config(WORKSPACE)
|
|
920
|
+
recommended_next_action = "cgc-install"
|
|
576
921
|
summary = build_mode_summary_payload(
|
|
577
922
|
scope="用户级 Claude 集成预演",
|
|
578
923
|
human_summary="已完成用户级 Claude 集成预演,未写入任何文件。",
|
|
@@ -602,7 +947,7 @@ def preview_user_install(override_root: str = "") -> dict[str, Any]:
|
|
|
602
947
|
},
|
|
603
948
|
"notes": [
|
|
604
949
|
"This mode does not modify user-level Claude files.",
|
|
605
|
-
"Use this preview
|
|
950
|
+
"Use this preview only when intentionally inspecting a user-level integration surface.",
|
|
606
951
|
"The preview includes MCP tool allow rules for codecgc, codex, and gemini servers.",
|
|
607
952
|
"Current CodeCGC product policy still defaults to project-local installation.",
|
|
608
953
|
],
|
|
@@ -620,7 +965,7 @@ def install_user_runtime(override_root: str = "") -> dict[str, Any]:
|
|
|
620
965
|
merged_settings, settings_changed = merge_hook_settings(settings, build_user_hook_command(user_paths))
|
|
621
966
|
merged_settings, permissions_changed = merge_permission_settings(merged_settings, DEFAULT_ALLOWED_TOOLS)
|
|
622
967
|
write_json_file(user_paths["settings"], merged_settings)
|
|
623
|
-
write_json_file(user_paths["mcp"], build_mcp_config())
|
|
968
|
+
write_json_file(user_paths["mcp"], build_mcp_config(WORKSPACE))
|
|
624
969
|
shutil.copyfile(PROJECT_HOOK_PATH, user_paths["hook_script"])
|
|
625
970
|
written_commands = write_custom_command_files(user_paths["commands_dir"], WORKSPACE / "bin")
|
|
626
971
|
summary = build_mode_summary_payload(
|
|
@@ -683,9 +1028,11 @@ def build_install_mode_summary(result: dict[str, Any]) -> str:
|
|
|
683
1028
|
f"- Claude 设置: {result.get('claude_settings', '')}",
|
|
684
1029
|
f"- Hook 脚本: {result.get('hook_script', '')}",
|
|
685
1030
|
f"- Slash Commands: {result.get('commands_dir', '')}",
|
|
1031
|
+
f"- 新手入口: {result.get('onboarding_file', '')}",
|
|
686
1032
|
"- 说明: 可选外部能力如 MemOS 不由 cgc-install 自动写入;如需启用,请在 Claude 中单独配置官方 MCP。",
|
|
687
1033
|
]
|
|
688
1034
|
next_actions = [
|
|
1035
|
+
"cgc-start",
|
|
689
1036
|
"cgc-status",
|
|
690
1037
|
"cgc-doctor",
|
|
691
1038
|
]
|
|
@@ -707,7 +1054,7 @@ def build_install_mode_summary(result: dict[str, Any]) -> str:
|
|
|
707
1054
|
next_actions = []
|
|
708
1055
|
user_root = str(result.get("user_claude_root", "")).strip()
|
|
709
1056
|
if user_root:
|
|
710
|
-
next_actions.append(
|
|
1057
|
+
next_actions.append("cgc-install")
|
|
711
1058
|
next_actions.append("cgc-install --mode status")
|
|
712
1059
|
return render_summary_block("CodeCGC 用户级预演", lines, next_actions)
|
|
713
1060
|
|
|
@@ -734,13 +1081,16 @@ def build_install_mode_summary(result: dict[str, Any]) -> str:
|
|
|
734
1081
|
|
|
735
1082
|
|
|
736
1083
|
def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
|
|
737
|
-
expected_mcp = build_mcp_config()
|
|
1084
|
+
expected_mcp = build_mcp_config(workspace_paths["root"])
|
|
738
1085
|
expected_hook_command = build_workspace_hook_command(workspace_paths)
|
|
739
1086
|
expected_hook_text = load_text_file(PROJECT_HOOK_PATH)
|
|
740
1087
|
current_settings = load_json_file(workspace_paths["settings"])
|
|
741
1088
|
current_mcp = load_json_file(workspace_paths["mcp"])
|
|
742
1089
|
current_hook_text = load_text_file(workspace_paths["hook_script"])
|
|
743
1090
|
routing_exists = workspace_paths["routing_file"].exists()
|
|
1091
|
+
policy_valid = policy_file_is_valid(workspace_paths["routing_file"]) if routing_exists else False
|
|
1092
|
+
workflow_dirs_ready = workspace_workflow_dirs_ready(workspace_paths["root"])
|
|
1093
|
+
onboarding_ready = onboarding_file_is_valid(workspace_paths["onboarding_file"])
|
|
744
1094
|
|
|
745
1095
|
hook_registered = settings_have_hook_command(current_settings, expected_hook_command)
|
|
746
1096
|
permissions_registered = settings_have_allowed_tools(current_settings, DEFAULT_ALLOWED_TOOLS)
|
|
@@ -750,6 +1100,10 @@ def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
750
1100
|
missing = []
|
|
751
1101
|
if not routing_exists:
|
|
752
1102
|
missing.append("routing_file")
|
|
1103
|
+
if routing_exists and not policy_valid:
|
|
1104
|
+
missing.append("routing_policy")
|
|
1105
|
+
if not workflow_dirs_ready:
|
|
1106
|
+
missing.append("workflow_dirs")
|
|
753
1107
|
if not mcp_matches:
|
|
754
1108
|
missing.append("mcp_json")
|
|
755
1109
|
if not hook_registered:
|
|
@@ -758,6 +1112,8 @@ def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
758
1112
|
missing.append("claude_settings_permissions")
|
|
759
1113
|
if not hook_file_matches:
|
|
760
1114
|
missing.append("hook_script")
|
|
1115
|
+
if not onboarding_ready:
|
|
1116
|
+
missing.append("onboarding_file")
|
|
761
1117
|
|
|
762
1118
|
ready = not missing
|
|
763
1119
|
return {
|
|
@@ -765,10 +1121,16 @@ def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
765
1121
|
"routing_file_path": str(workspace_paths["routing_file"]),
|
|
766
1122
|
"claude_settings_path": str(workspace_paths["settings"]),
|
|
767
1123
|
"hook_script_path": str(workspace_paths["hook_script"]),
|
|
1124
|
+
"onboarding_file_path": str(workspace_paths["onboarding_file"]),
|
|
768
1125
|
"mcp_json_exists": workspace_paths["mcp"].exists(),
|
|
769
1126
|
"routing_file_exists": routing_exists,
|
|
1127
|
+
"routing_policy_valid": policy_valid,
|
|
1128
|
+
"workflow_dirs_ready": workflow_dirs_ready,
|
|
1129
|
+
"onboarding_ready": onboarding_ready,
|
|
1130
|
+
"workflow_dirs_expected": [str(workspace_paths["root"] / item) for item in PROJECT_WORKFLOW_DIRS],
|
|
770
1131
|
"claude_settings_exists": workspace_paths["settings"].exists(),
|
|
771
1132
|
"hook_exists": workspace_paths["hook_script"].exists(),
|
|
1133
|
+
"onboarding_exists": workspace_paths["onboarding_file"].exists(),
|
|
772
1134
|
"mcp_matches_expected": mcp_matches,
|
|
773
1135
|
"hook_registered": hook_registered,
|
|
774
1136
|
"permissions_registered": permissions_registered,
|
|
@@ -782,7 +1144,7 @@ def collect_project_status(workspace_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
782
1144
|
|
|
783
1145
|
|
|
784
1146
|
def collect_user_status(user_paths: dict[str, Path]) -> dict[str, Any]:
|
|
785
|
-
expected_mcp = build_mcp_config()
|
|
1147
|
+
expected_mcp = build_mcp_config(WORKSPACE)
|
|
786
1148
|
expected_hook_command = build_user_hook_command(user_paths)
|
|
787
1149
|
expected_hook_text = load_text_file(PROJECT_HOOK_PATH)
|
|
788
1150
|
current_settings = load_json_file(user_paths["settings"])
|
|
@@ -791,7 +1153,9 @@ def collect_user_status(user_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
791
1153
|
|
|
792
1154
|
hook_registered = settings_have_hook_command(current_settings, expected_hook_command)
|
|
793
1155
|
permissions_registered = settings_have_allowed_tools(current_settings, DEFAULT_ALLOWED_TOOLS)
|
|
794
|
-
|
|
1156
|
+
mcp_matches_exact = current_mcp == expected_mcp if user_paths["mcp"].exists() else False
|
|
1157
|
+
mcp_matches_runtime = mcp_config_matches_runtime_shape(current_mcp) if user_paths["mcp"].exists() else False
|
|
1158
|
+
mcp_matches = mcp_matches_exact or mcp_matches_runtime
|
|
795
1159
|
hook_file_matches = current_hook_text == expected_hook_text if user_paths["hook_script"].exists() else False
|
|
796
1160
|
|
|
797
1161
|
missing = []
|
|
@@ -814,6 +1178,8 @@ def collect_user_status(user_paths: dict[str, Path]) -> dict[str, Any]:
|
|
|
814
1178
|
"mcp_exists": user_paths["mcp"].exists(),
|
|
815
1179
|
"hook_exists": user_paths["hook_script"].exists(),
|
|
816
1180
|
"mcp_matches_expected": mcp_matches,
|
|
1181
|
+
"mcp_matches_expected_exact": mcp_matches_exact,
|
|
1182
|
+
"mcp_matches_runtime_shape": mcp_matches_runtime,
|
|
817
1183
|
"hook_registered": hook_registered,
|
|
818
1184
|
"permissions_registered": permissions_registered,
|
|
819
1185
|
"hook_file_matches_expected": hook_file_matches,
|
|
@@ -845,6 +1211,20 @@ def collect_install_status(override_workspace: str = "") -> dict[str, Any]:
|
|
|
845
1211
|
"scope": "项目级集成就绪状态,以及用户级 Claude 集成预演状态",
|
|
846
1212
|
}
|
|
847
1213
|
|
|
1214
|
+
status_summary.update(
|
|
1215
|
+
{
|
|
1216
|
+
"default_policy": "project-local-first",
|
|
1217
|
+
"recommended_next_command": project_status["recommended_command"],
|
|
1218
|
+
"recommended_user_command": "",
|
|
1219
|
+
"human_summary": (
|
|
1220
|
+
"Project-local CodeCGC integration is ready."
|
|
1221
|
+
if project_status["ready"]
|
|
1222
|
+
else "Project-local CodeCGC integration is missing or outdated."
|
|
1223
|
+
),
|
|
1224
|
+
"scope": "project-local integration status; user-level config is optional preview only",
|
|
1225
|
+
}
|
|
1226
|
+
)
|
|
1227
|
+
|
|
848
1228
|
return {
|
|
849
1229
|
"success": True,
|
|
850
1230
|
"mode": "status",
|
|
@@ -856,6 +1236,51 @@ def collect_install_status(override_workspace: str = "") -> dict[str, Any]:
|
|
|
856
1236
|
}
|
|
857
1237
|
|
|
858
1238
|
|
|
1239
|
+
def collect_start_status(override_workspace: str = "") -> dict[str, Any]:
|
|
1240
|
+
workspace_paths = get_workspace_paths(override_workspace)
|
|
1241
|
+
project_status = collect_project_status(workspace_paths)
|
|
1242
|
+
onboarding_path = workspace_paths["onboarding_file"]
|
|
1243
|
+
onboarding_ready = onboarding_file_is_valid(onboarding_path)
|
|
1244
|
+
guide_text = load_text_file(onboarding_path) if onboarding_ready else ""
|
|
1245
|
+
recommended_next_action = "cgc-status" if onboarding_ready else build_workspace_install_command(workspace_paths["root"])
|
|
1246
|
+
human_summary = (
|
|
1247
|
+
"CodeCGC first-run guide is ready. Start with /cgc or cgc after status/doctor are green."
|
|
1248
|
+
if onboarding_ready
|
|
1249
|
+
else "CodeCGC first-run guide is missing or outdated. Run project-local install first."
|
|
1250
|
+
)
|
|
1251
|
+
quick_actions = [
|
|
1252
|
+
"/cgc-status",
|
|
1253
|
+
"/cgc-doctor",
|
|
1254
|
+
"/cgc <你的需求>",
|
|
1255
|
+
"cgc-status",
|
|
1256
|
+
"cgc-doctor",
|
|
1257
|
+
"cgc \"你的需求\"",
|
|
1258
|
+
]
|
|
1259
|
+
if not onboarding_ready:
|
|
1260
|
+
quick_actions = ["/cgc-install", build_workspace_install_command(workspace_paths["root"])]
|
|
1261
|
+
|
|
1262
|
+
return {
|
|
1263
|
+
"success": True,
|
|
1264
|
+
"mode": "start",
|
|
1265
|
+
"workspace": str(workspace_paths["root"]),
|
|
1266
|
+
"summary": {
|
|
1267
|
+
"ready": onboarding_ready,
|
|
1268
|
+
"human_summary": human_summary,
|
|
1269
|
+
"scope": "project-local first-run guide and next actions",
|
|
1270
|
+
"recommended_next_action": recommended_next_action,
|
|
1271
|
+
"quick_actions": quick_actions,
|
|
1272
|
+
},
|
|
1273
|
+
"onboarding": {
|
|
1274
|
+
"path": str(onboarding_path),
|
|
1275
|
+
"exists": onboarding_path.exists(),
|
|
1276
|
+
"ready": onboarding_ready,
|
|
1277
|
+
"relative_path": PROJECT_ONBOARDING_RELATIVE_PATH,
|
|
1278
|
+
"guide_excerpt": guide_text[:1200],
|
|
1279
|
+
},
|
|
1280
|
+
"project": project_status,
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
|
|
859
1284
|
def find_python_command() -> str:
|
|
860
1285
|
candidates = ["python", "py"] if os.name == "nt" else ["python3", "python"]
|
|
861
1286
|
for candidate in candidates:
|
|
@@ -1065,11 +1490,20 @@ def classify_doctor_failures(
|
|
|
1065
1490
|
)
|
|
1066
1491
|
continue
|
|
1067
1492
|
|
|
1068
|
-
if name
|
|
1493
|
+
if name == "onboarding_file_ready":
|
|
1494
|
+
add_failure(
|
|
1495
|
+
"onboarding-guide-missing",
|
|
1496
|
+
"项目级新手入口文件缺失或已过期。",
|
|
1497
|
+
"重新执行项目级安装以同步 `codecgc/START_HERE.md` 与 `/cgc-start` 入口。",
|
|
1498
|
+
install_command,
|
|
1499
|
+
)
|
|
1500
|
+
continue
|
|
1501
|
+
|
|
1502
|
+
if name in {"routing_file_exists", "routing_policy_valid", "project_hook_source_exists"}:
|
|
1069
1503
|
add_failure(
|
|
1070
1504
|
"packaged-runtime-missing-files",
|
|
1071
|
-
"
|
|
1072
|
-
"
|
|
1505
|
+
"运行时所需的 policy、路由文件或 hook 源文件缺失/无效。",
|
|
1506
|
+
"重新执行项目级安装以同步 model-routing.yaml、policy-backed hook 与 Claude settings。",
|
|
1073
1507
|
)
|
|
1074
1508
|
continue
|
|
1075
1509
|
|
|
@@ -1104,6 +1538,21 @@ def collect_doctor_status(override_workspace: str = "") -> dict[str, Any]:
|
|
|
1104
1538
|
"ok": workspace_paths["routing_file"].exists(),
|
|
1105
1539
|
"detail": str(workspace_paths["routing_file"]),
|
|
1106
1540
|
},
|
|
1541
|
+
{
|
|
1542
|
+
"name": "routing_policy_valid",
|
|
1543
|
+
"ok": policy_file_is_valid(workspace_paths["routing_file"]) if workspace_paths["routing_file"].exists() else False,
|
|
1544
|
+
"detail": str(workspace_paths["routing_file"]),
|
|
1545
|
+
},
|
|
1546
|
+
{
|
|
1547
|
+
"name": "workflow_dirs_ready",
|
|
1548
|
+
"ok": workspace_workflow_dirs_ready(workspace_paths["root"]),
|
|
1549
|
+
"detail": str(workspace_paths["root"] / "codecgc"),
|
|
1550
|
+
},
|
|
1551
|
+
{
|
|
1552
|
+
"name": "onboarding_file_ready",
|
|
1553
|
+
"ok": onboarding_file_is_valid(workspace_paths["onboarding_file"]),
|
|
1554
|
+
"detail": str(workspace_paths["onboarding_file"]),
|
|
1555
|
+
},
|
|
1107
1556
|
{
|
|
1108
1557
|
"name": "project_hook_source_exists",
|
|
1109
1558
|
"ok": PROJECT_HOOK_PATH.exists(),
|
|
@@ -1204,7 +1653,7 @@ def collect_doctor_status(override_workspace: str = "") -> dict[str, Any]:
|
|
|
1204
1653
|
|
|
1205
1654
|
def build_parser() -> argparse.ArgumentParser:
|
|
1206
1655
|
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")
|
|
1656
|
+
parser.add_argument("--mode", choices=["local", "user-dry-run", "user", "status", "doctor", "start"], default="local")
|
|
1208
1657
|
parser.add_argument(
|
|
1209
1658
|
"--format",
|
|
1210
1659
|
choices=["json", "summary"],
|
|
@@ -1234,9 +1683,28 @@ def main() -> int:
|
|
|
1234
1683
|
result = collect_install_status(args.workspace)
|
|
1235
1684
|
elif args.mode == "doctor":
|
|
1236
1685
|
result = collect_doctor_status(args.workspace)
|
|
1686
|
+
elif args.mode == "start":
|
|
1687
|
+
result = collect_start_status(args.workspace)
|
|
1237
1688
|
else:
|
|
1238
1689
|
raise ValueError(f"Unsupported install mode: {args.mode}")
|
|
1239
1690
|
|
|
1691
|
+
if args.mode == "start" and args.format == "summary":
|
|
1692
|
+
summary = result.get("summary", {}) if isinstance(result.get("summary"), dict) else {}
|
|
1693
|
+
onboarding = result.get("onboarding", {}) if isinstance(result.get("onboarding"), dict) else {}
|
|
1694
|
+
quick_actions = summary.get("quick_actions", []) if isinstance(summary.get("quick_actions"), list) else []
|
|
1695
|
+
lines = [
|
|
1696
|
+
f"- 工作区: {result.get('workspace', '')}",
|
|
1697
|
+
f"- 范围: {summary.get('scope', '')}",
|
|
1698
|
+
f"- 新手入口就绪: {format_bool_zh(summary.get('ready'))}",
|
|
1699
|
+
f"- 入口文件: {onboarding.get('path', '')}",
|
|
1700
|
+
f"- 摘要: {summary.get('human_summary', '')}",
|
|
1701
|
+
f"- 快速动作: {format_list_zh(quick_actions)}",
|
|
1702
|
+
]
|
|
1703
|
+
next_action = str(summary.get("recommended_next_action", "")).strip()
|
|
1704
|
+
next_actions = [next_action] if next_action else []
|
|
1705
|
+
print(render_summary_block("CodeCGC Start", lines, next_actions))
|
|
1706
|
+
return 0 if result.get("success") else 1
|
|
1707
|
+
|
|
1240
1708
|
if args.mode == "status" and args.format == "summary":
|
|
1241
1709
|
summary = result.get("summary", {}) if isinstance(result.get("summary"), dict) else {}
|
|
1242
1710
|
project = result.get("project", {}) if isinstance(result.get("project"), dict) else {}
|
|
@@ -1249,6 +1717,7 @@ def main() -> int:
|
|
|
1249
1717
|
f"- 策略: {summary.get('default_policy', '')}",
|
|
1250
1718
|
f"- 摘要: {summary.get('human_summary', '')}",
|
|
1251
1719
|
f"- 项目级缺失项: {format_list_zh(project.get('missing_or_outdated', []))}",
|
|
1720
|
+
f"- 新手入口: {project.get('onboarding_file_path', '')}",
|
|
1252
1721
|
f"- 用户级缺失项: {format_list_zh(user.get('missing_or_outdated', []))}",
|
|
1253
1722
|
]
|
|
1254
1723
|
recommended_project = str(summary.get("recommended_project_command", "")).strip()
|