@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.
Files changed (71) hide show
  1. package/.claude/hooks/route-edit.ps1 +58 -57
  2. package/INSTALLATION.md +122 -484
  3. package/README.md +124 -149
  4. package/bin/cgc-external-status.js +4 -0
  5. package/bin/cgc-start.js +4 -0
  6. package/bin/codecgc.js +141 -20
  7. package/codecgc/compound/codecgc-capability-matrix.md +37 -0
  8. package/codecgc/reference/README.md +69 -0
  9. package/codecgc/reference/execution-model.md +3 -1
  10. package/codecgc/reference/maintainer-guide.md +93 -0
  11. package/codecgc/reference/mcp-tool-surface.md +112 -0
  12. package/codecgc/reference/onboarding.md +69 -0
  13. package/codecgc/reference/operation-guide.md +29 -23
  14. package/codecgc/reference/path-contract.md +53 -0
  15. package/codecgc/reference/policy-routing.md +58 -0
  16. package/codecgc/reference/project-structure.md +87 -0
  17. package/codecgc/reference/quickstart.md +110 -0
  18. package/codecgc/reference/real-workflow-loop.md +123 -0
  19. package/codecgc/reference/recovery-loop.md +109 -0
  20. package/codecgc/reference/release-maintenance-playbook.md +4 -1
  21. package/codecgc/reference/troubleshooting.md +114 -0
  22. package/codecgc/roadmap/codecgc-release-maintenance/delivery-plan.md +49 -0
  23. package/codecgc/roadmap/codecgc-release-maintenance/overview.md +41 -0
  24. package/codecgc/roadmap/codecgc-release-maintenance/phases.md +84 -0
  25. package/codecgc/templates/claude/settings.local.json +27 -0
  26. package/codecgc/templates/codex/codecgcrc.json +22 -0
  27. package/codecgc/templates/gemini/codecgc-policy.toml +47 -0
  28. package/codecgcmcp/README.md +57 -11
  29. package/codecgcmcp/src/codecgcmcp/server.py +164 -26
  30. package/codexmcp/src/codexmcp/server.py +45 -0
  31. package/geminimcp/src/geminimcp/server.py +106 -24
  32. package/model-routing.yaml +31 -6
  33. package/package.json +12 -4
  34. package/scripts/audit_codecgc_external_capabilities.py +83 -4
  35. package/scripts/audit_codecgc_historical_audits.py +42 -2
  36. package/scripts/audit_codecgc_package_runtime.py +76 -4
  37. package/scripts/audit_codecgc_release_readiness.py +55 -3
  38. package/scripts/audit_codecgc_workflow_history.py +8 -5
  39. package/scripts/build_codecgc_task.py +69 -45
  40. package/scripts/codecgc_artifact_roots.py +8 -40
  41. package/scripts/codecgc_console_io.py +3 -45
  42. package/scripts/codecgc_executor_registry.py +4 -54
  43. package/scripts/codecgc_path_contract.py +7 -0
  44. package/scripts/codecgc_policy.py +447 -0
  45. package/scripts/codecgc_routing_paths.py +3 -16
  46. package/scripts/codecgc_routing_template.py +11 -135
  47. package/scripts/codecgc_runtime/__init__.py +1 -0
  48. package/scripts/codecgc_runtime/artifacts.py +42 -0
  49. package/scripts/codecgc_runtime/console.py +45 -0
  50. package/scripts/codecgc_runtime/executor_registry.py +55 -0
  51. package/scripts/codecgc_runtime/mcp_config.py +72 -0
  52. package/scripts/codecgc_runtime/path_contract.py +123 -0
  53. package/scripts/codecgc_runtime/paths.py +22 -0
  54. package/scripts/codecgc_runtime/routing_paths.py +16 -0
  55. package/scripts/codecgc_runtime/routing_template.py +171 -0
  56. package/scripts/codecgc_runtime/workflow_runtime.py +72 -0
  57. package/scripts/codecgc_runtime_paths.py +3 -22
  58. package/scripts/codecgc_step_control.py +3 -2
  59. package/scripts/codecgc_workflow_runtime.py +3 -71
  60. package/scripts/entry_codecgc_workflow.py +4 -0
  61. package/scripts/install_codecgc.py +560 -32
  62. package/scripts/normalize_codecgc_audits.py +5 -3
  63. package/scripts/postinstall_codecgc.js +6 -56
  64. package/scripts/review_codecgc_workflow.py +6 -3
  65. package/scripts/route_codecgc_workflow.py +67 -36
  66. package/scripts/run_codecgc_build.py +28 -0
  67. package/scripts/run_codecgc_fix.py +28 -0
  68. package/scripts/run_codecgc_task.py +5 -2
  69. package/scripts/run_codecgc_test.py +28 -0
  70. package/scripts/sync_codecgc_mcp_config.py +4 -54
  71. 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": "Edit|Write",
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": "Edit|Write",
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, False
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") != "Edit|Write":
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
- return "powershell -ExecutionPolicy Bypass -File .claude/hooks/route-edit.ps1"
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
- settings = load_json_file(workspace_paths["settings"])
523
- merged_settings, settings_changed = merge_hook_settings(
524
- settings,
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
- merged_settings, permissions_changed = merge_permission_settings(merged_settings, DEFAULT_ALLOWED_TOOLS)
528
- if settings_changed or not workspace_paths["settings"].exists():
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-status",
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 and preserves custom path blocks.",
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 MCP tool permissions were merged into project .claude/settings.json.",
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
- return f"powershell -ExecutionPolicy Bypass -File {user_paths['hook_script']}"
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 = f"cgc-install --mode user --user-root {shell_quote(str(user_paths['root']))}"
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 to inspect the future user-level integration surface.",
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(f"cgc-install --mode user --user-root {shell_quote(user_root)}")
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
- mcp_matches = current_mcp == expected_mcp if user_paths["mcp"].exists() else False
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 in {"routing_file_exists", "project_hook_source_exists"}:
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
- "运行时所需的路由文件或 hook 源文件缺失。",
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()