@hunyed15/codecgc 0.1.0

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 (128) hide show
  1. package/.claude/hooks/route-edit.ps1 +86 -0
  2. package/INSTALLATION.md +550 -0
  3. package/LICENSE +21 -0
  4. package/README.md +171 -0
  5. package/bin/cgc-build.js +4 -0
  6. package/bin/cgc-doctor.js +4 -0
  7. package/bin/cgc-entry.js +4 -0
  8. package/bin/cgc-external-audit.js +4 -0
  9. package/bin/cgc-fix.js +4 -0
  10. package/bin/cgc-history.js +4 -0
  11. package/bin/cgc-install.js +4 -0
  12. package/bin/cgc-lifecycle.js +4 -0
  13. package/bin/cgc-package-audit.js +4 -0
  14. package/bin/cgc-plan.js +4 -0
  15. package/bin/cgc-release-readiness.js +4 -0
  16. package/bin/cgc-review.js +4 -0
  17. package/bin/cgc-route.js +4 -0
  18. package/bin/cgc-status.js +4 -0
  19. package/bin/cgc-test.js +4 -0
  20. package/bin/cgc.js +4 -0
  21. package/bin/codecgc.js +1284 -0
  22. package/codecgc/cgc/SKILL.md +46 -0
  23. package/codecgc/cgc-arch/SKILL.md +61 -0
  24. package/codecgc/cgc-build/SKILL.md +53 -0
  25. package/codecgc/cgc-decide/SKILL.md +55 -0
  26. package/codecgc/cgc-fix/SKILL.md +47 -0
  27. package/codecgc/cgc-learn/SKILL.md +46 -0
  28. package/codecgc/cgc-onboard/SKILL.md +52 -0
  29. package/codecgc/cgc-plan/SKILL.md +48 -0
  30. package/codecgc/cgc-refactor/SKILL.md +46 -0
  31. package/codecgc/cgc-req/SKILL.md +61 -0
  32. package/codecgc/cgc-review/SKILL.md +57 -0
  33. package/codecgc/cgc-roadmap/SKILL.md +55 -0
  34. package/codecgc/cgc-test/SKILL.md +21 -0
  35. package/codecgc/reference/api-cgc-review-libdoc.md +13 -0
  36. package/codecgc/reference/artifact-class-policy.md +81 -0
  37. package/codecgc/reference/build-flow.md +95 -0
  38. package/codecgc/reference/checklist-contract.md +103 -0
  39. package/codecgc/reference/execution-audit.md +121 -0
  40. package/codecgc/reference/execution-model.md +118 -0
  41. package/codecgc/reference/execution-routing.md +130 -0
  42. package/codecgc/reference/executor-contract.md +87 -0
  43. package/codecgc/reference/external-capability-registry.json +104 -0
  44. package/codecgc/reference/fix-flow.md +94 -0
  45. package/codecgc/reference/fixture-governance.md +60 -0
  46. package/codecgc/reference/flow-execution.md +65 -0
  47. package/codecgc/reference/lifecycle-map.md +172 -0
  48. package/codecgc/reference/lifecycle-playbook.md +104 -0
  49. package/codecgc/reference/long-lived-artifacts.md +98 -0
  50. package/codecgc/reference/operation-guide.md +242 -0
  51. package/codecgc/reference/release-maintenance-playbook.md +150 -0
  52. package/codecgc/reference/review-writeback.md +141 -0
  53. package/codecgc/reference/role-model.md +128 -0
  54. package/codecgc/reference/runtime-boundary.md +72 -0
  55. package/codecgc/reference/shared-conventions.md +93 -0
  56. package/codecgc/reference/workflow-scaffold.md +57 -0
  57. package/codexmcp/LICENSE +21 -0
  58. package/codexmcp/README.md +294 -0
  59. package/codexmcp/pyproject.toml +37 -0
  60. package/codexmcp/src/codexmcp/__init__.py +4 -0
  61. package/codexmcp/src/codexmcp/cli.py +12 -0
  62. package/codexmcp/src/codexmcp/server.py +529 -0
  63. package/geminimcp/README.md +258 -0
  64. package/geminimcp/pyproject.toml +15 -0
  65. package/geminimcp/src/geminimcp/__init__.py +4 -0
  66. package/geminimcp/src/geminimcp/cli.py +12 -0
  67. package/geminimcp/src/geminimcp/server.py +465 -0
  68. package/model-routing.yaml +30 -0
  69. package/package.json +90 -0
  70. package/requirements.txt +1 -0
  71. package/scripts/README-codecgc-cli.md +89 -0
  72. package/scripts/audit_codecgc_external_capabilities.py +276 -0
  73. package/scripts/audit_codecgc_historical_audits.py +242 -0
  74. package/scripts/audit_codecgc_lifecycle.py +241 -0
  75. package/scripts/audit_codecgc_package_runtime.py +445 -0
  76. package/scripts/audit_codecgc_release_readiness.py +202 -0
  77. package/scripts/audit_codecgc_review_policy.py +82 -0
  78. package/scripts/audit_codecgc_workflow_history.py +317 -0
  79. package/scripts/build_codecgc_task.py +487 -0
  80. package/scripts/codecgc_artifact_roots.py +40 -0
  81. package/scripts/codecgc_cli.py +843 -0
  82. package/scripts/codecgc_command_surface.py +28 -0
  83. package/scripts/codecgc_console_io.py +45 -0
  84. package/scripts/codecgc_executor_registry.py +54 -0
  85. package/scripts/codecgc_file_evidence.py +349 -0
  86. package/scripts/codecgc_flow_control.py +233 -0
  87. package/scripts/codecgc_governance_dedupe.py +161 -0
  88. package/scripts/codecgc_plan_decision.py +103 -0
  89. package/scripts/codecgc_review_control.py +588 -0
  90. package/scripts/codecgc_roadmap_templates.py +149 -0
  91. package/scripts/codecgc_routing_paths.py +16 -0
  92. package/scripts/codecgc_routing_template.py +135 -0
  93. package/scripts/codecgc_runtime_paths.py +22 -0
  94. package/scripts/codecgc_session_recovery.py +44 -0
  95. package/scripts/codecgc_step_control.py +154 -0
  96. package/scripts/codecgc_workflow_runtime.py +63 -0
  97. package/scripts/codecgc_workflow_templates.py +437 -0
  98. package/scripts/entry_codecgc_workflow.py +3419 -0
  99. package/scripts/exercise_mcp_tools.py +109 -0
  100. package/scripts/expand_codecgc_roadmap.py +664 -0
  101. package/scripts/init_codecgc_roadmap.py +134 -0
  102. package/scripts/init_codecgc_workflow.py +207 -0
  103. package/scripts/install_codecgc.py +938 -0
  104. package/scripts/migrate_demo_workflows_to_fixtures.py +128 -0
  105. package/scripts/normalize_codecgc_audits.py +114 -0
  106. package/scripts/normalize_codecgc_governance_docs.py +79 -0
  107. package/scripts/normalize_codecgc_workflow_docs.py +269 -0
  108. package/scripts/plan_codecgc_workflow.py +970 -0
  109. package/scripts/refresh_codecgc_review_policy.py +223 -0
  110. package/scripts/review_codecgc_workflow.py +88 -0
  111. package/scripts/route_codecgc_workflow.py +671 -0
  112. package/scripts/run_codecgc_build.py +104 -0
  113. package/scripts/run_codecgc_fix.py +104 -0
  114. package/scripts/run_codecgc_flow_step.py +165 -0
  115. package/scripts/run_codecgc_task.py +410 -0
  116. package/scripts/run_codecgc_test.py +105 -0
  117. package/scripts/sync_codecgc_mcp_config.py +41 -0
  118. package/scripts/write_codecgc_architecture.py +78 -0
  119. package/scripts/write_codecgc_decision.py +83 -0
  120. package/scripts/write_codecgc_explore.py +118 -0
  121. package/scripts/write_codecgc_guide.py +141 -0
  122. package/scripts/write_codecgc_learning.py +87 -0
  123. package/scripts/write_codecgc_libdoc.py +140 -0
  124. package/scripts/write_codecgc_refactor.py +78 -0
  125. package/scripts/write_codecgc_requirement.py +78 -0
  126. package/scripts/write_codecgc_review.py +291 -0
  127. package/scripts/write_codecgc_roadmap.py +122 -0
  128. package/scripts/write_codecgc_trick.py +123 -0
@@ -0,0 +1,671 @@
1
+ import argparse
2
+ import json
3
+ import re
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from build_codecgc_task import build_split_required_payload
8
+ from build_codecgc_task import load_checklist_yaml
9
+ from build_codecgc_task import load_simple_routing_config
10
+ from codecgc_artifact_roots import discover_flow_directory
11
+ from codecgc_artifact_roots import execution_root
12
+ from codecgc_artifact_roots import normalize_artifact_class
13
+ from codecgc_command_surface import to_public_command
14
+ from codecgc_console_io import configure_utf8_stdio
15
+ from codecgc_console_io import print_json
16
+ from codecgc_routing_paths import resolve_active_routing_file
17
+ from codecgc_runtime_paths import PACKAGE_ROOT
18
+ from codecgc_step_control import is_test_codecgc_block
19
+ from codecgc_step_control import get_step_metadata
20
+ from codecgc_step_control import select_next_executable_step
21
+
22
+ WORKSPACE = PACKAGE_ROOT
23
+ ROUTING_FILE = resolve_active_routing_file()
24
+
25
+
26
+ def attach_route_summary(result: dict[str, Any]) -> dict[str, Any]:
27
+ review = result.get("review", {}) if isinstance(result.get("review"), dict) else {}
28
+ current_step = result.get("current_step", {}) if isinstance(result.get("current_step"), dict) else {}
29
+ recommended_command = str(result.get("recommended_command", "")).strip()
30
+ next_text = str(result.get("next", "")).strip()
31
+ human_summary = str(result.get("reason", "")).strip() or next_text
32
+ workflow_state = "closed" if not recommended_command and result.get("success") else ""
33
+ if recommended_command == "cgc-plan":
34
+ workflow_state = "needs-planning"
35
+ elif recommended_command == "cgc-build":
36
+ workflow_state = "awaiting-build"
37
+ elif recommended_command == "cgc-fix":
38
+ workflow_state = "awaiting-fix"
39
+ elif recommended_command == "cgc-review":
40
+ workflow_state = "awaiting-review"
41
+ elif current_step:
42
+ workflow_state = "step-selected"
43
+
44
+ result["summary"] = {
45
+ "human_summary": human_summary,
46
+ "recommended_command": recommended_command,
47
+ "next": next_text,
48
+ "workflow_state": workflow_state,
49
+ "current_step_number": int(current_step.get("step_number", 0) or 0),
50
+ "current_task_id": str(current_step.get("task_id", "")).strip(),
51
+ "review_decision": str(review.get("decision", "")).strip(),
52
+ "is_closed": workflow_state == "closed",
53
+ }
54
+ return result
55
+
56
+
57
+ def build_parser() -> argparse.ArgumentParser:
58
+ parser = argparse.ArgumentParser(
59
+ description="Route an existing CodeCGC feature or issue artifact directory to the recommended next command."
60
+ )
61
+ parser.add_argument("--flow", required=True, choices=["feature", "issue"])
62
+ parser.add_argument("--slug", required=True)
63
+ return parser
64
+
65
+
66
+ def load_text(path: Path) -> str:
67
+ return path.read_text(encoding="utf-8") if path.exists() else ""
68
+
69
+
70
+ def load_json(path: Path) -> dict[str, Any] | None:
71
+ if not path.exists():
72
+ return None
73
+ data = json.loads(path.read_text(encoding="utf-8"))
74
+ return data if isinstance(data, dict) else None
75
+
76
+
77
+ def find_artifact_file(directory: Path, suffix: str) -> Path | None:
78
+ matches = sorted(directory.glob(f"*{suffix}"))
79
+ return matches[0] if matches else None
80
+
81
+
82
+ def checklist_has_codecgc_step(path: Path) -> bool:
83
+ text = load_text(path)
84
+ return "codecgc:" in text and "steps:" in text
85
+
86
+
87
+ def checklist_has_planning_step(path: Path) -> bool:
88
+ text = load_text(path)
89
+ return "planning_note:" in text or "owner_hint: Claude planning" in text
90
+
91
+
92
+ def extract_task_ids(path: Path) -> list[str]:
93
+ task_ids: list[str] = []
94
+ pattern = re.compile(r"^\s*task_id:\s*(.+?)\s*$")
95
+ for line in load_text(path).splitlines():
96
+ match = pattern.match(line)
97
+ if not match:
98
+ continue
99
+ task_id = match.group(1).strip().strip("\"'")
100
+ if task_id:
101
+ task_ids.append(task_id)
102
+ return task_ids
103
+
104
+
105
+ def first_pending_step_is_not_executable(path: Path) -> tuple[bool, dict[str, Any] | None]:
106
+ data = load_text(path)
107
+ if "steps:" not in data:
108
+ return False, None
109
+
110
+ try:
111
+ checklist_data = load_checklist_yaml(path)
112
+ except Exception:
113
+ return False, None
114
+
115
+ steps = checklist_data.get("steps", [])
116
+ if not isinstance(steps, list) or not steps:
117
+ return False, None
118
+
119
+ for index, step in enumerate(steps, start=1):
120
+ if not isinstance(step, dict):
121
+ continue
122
+ status = str(step.get("status", "pending")).strip().lower()
123
+ if status not in {"pending", ""}:
124
+ continue
125
+ try:
126
+ metadata = get_step_metadata(path, index)
127
+ except Exception:
128
+ return False, None
129
+ if metadata.get("executable"):
130
+ return False, metadata
131
+ return True, metadata
132
+
133
+ return False, None
134
+
135
+
136
+ def build_route_scope_split_payload(step: dict[str, Any]) -> dict[str, Any]:
137
+ kind = str(step.get("kind", "")).strip().lower()
138
+ target_paths = step.get("target_paths", [])
139
+ if kind != "auto" or not isinstance(target_paths, list) or not target_paths:
140
+ return {}
141
+
142
+ normalized_paths = [str(path).strip().replace("\\", "/") for path in target_paths if str(path).strip()]
143
+ if not normalized_paths:
144
+ return {}
145
+
146
+ routing = load_simple_routing_config(ROUTING_FILE)
147
+ payload = build_split_required_payload(normalized_paths, routing)
148
+ grouped_paths = payload.get("grouped_paths", {}) if isinstance(payload.get("grouped_paths"), dict) else {}
149
+ has_shared = bool(grouped_paths.get("shared"))
150
+ has_frontend = bool(grouped_paths.get("frontend"))
151
+ has_backend = bool(grouped_paths.get("backend"))
152
+ if not (has_shared or (has_frontend and has_backend)):
153
+ return {}
154
+
155
+ in_scope: list[str] = []
156
+ for item in payload.get("suggested_split_steps", []):
157
+ if not isinstance(item, dict):
158
+ continue
159
+ item_kind = str(item.get("kind", "")).strip()
160
+ paths = [str(path).strip() for path in item.get("target_paths", []) if str(path).strip()]
161
+ if not paths:
162
+ continue
163
+ if item_kind == "planning":
164
+ in_scope.append(f"先拆分 shared 路径:{', '.join(paths)}。")
165
+ else:
166
+ in_scope.append(f"将 {item_kind} 范围拆成独立步骤:{', '.join(paths)}。")
167
+
168
+ return {
169
+ "split_suggestion": payload,
170
+ "replan_payload": {
171
+ "kind": "auto",
172
+ "split_scope": True,
173
+ "target_paths": normalized_paths,
174
+ "path_classification": payload.get("path_classification", {}),
175
+ "grouped_paths": grouped_paths,
176
+ "suggested_split_steps": payload.get("suggested_split_steps", []),
177
+ "in_scope": in_scope,
178
+ },
179
+ }
180
+
181
+
182
+ def find_audit_for_task_id(task_id: str, artifact_class: str) -> tuple[Path | None, dict[str, Any] | None]:
183
+ if not task_id:
184
+ return None, None
185
+ audit_path = execution_root(artifact_class) / f"{task_id}.json"
186
+ audit = load_json(audit_path)
187
+ if not audit:
188
+ return None, None
189
+ return audit_path, audit
190
+
191
+
192
+ def extract_review_metadata(path: Path) -> dict[str, Any]:
193
+ text = load_text(path)
194
+ decision_match = re.search(
195
+ r"## [45]\. (?:Review Decision|审核结论)\s+[\r\n]+- (?:审核结果:\s*)?(accepted|changes-requested|通过|需修改)",
196
+ text,
197
+ )
198
+ if not decision_match:
199
+ decision_match = re.search(r"- (?:Final decision|最终决策): (accepted|changes-requested|通过|需修改)", text)
200
+ task_match = re.search(r"- (?:Reviewed task_id|审核 task_id): (.+)", text)
201
+ step_match = re.search(r"- (?:Reviewed step_number|审核 step_number|审核步骤序号): (\d+)", text)
202
+ action_kind_match = re.search(r"- (?:Review action kind|审核动作类型): (.+)", text)
203
+ fallback_stage_match = re.search(r"- (?:Review fallback stage|审核回退阶段): (.+)", text)
204
+ policy_reason_match = re.search(r"- (?:Review policy reason|审核策略原因): (.+)", text)
205
+ raw_decision = decision_match.group(1).strip() if decision_match else ""
206
+ decision = {"通过": "accepted", "需修改": "changes-requested"}.get(raw_decision, raw_decision)
207
+ return {
208
+ "decision": decision,
209
+ "task_id": task_match.group(1).strip() if task_match else "",
210
+ "step_number": int(step_match.group(1)) if step_match else 0,
211
+ "action_kind": action_kind_match.group(1).strip() if action_kind_match else "",
212
+ "fallback_stage": fallback_stage_match.group(1).strip() if fallback_stage_match else "",
213
+ "policy_reason": policy_reason_match.group(1).strip() if policy_reason_match else "",
214
+ }
215
+
216
+
217
+ def review_matches_step(review: dict[str, Any], step: dict[str, Any]) -> bool:
218
+ return (
219
+ str(review.get("task_id", "")) == str(step.get("task_id", ""))
220
+ and int(review.get("step_number", 0) or 0) == int(step.get("step_number", 0) or 0)
221
+ )
222
+
223
+
224
+ def audit_is_ready_for_review(audit: dict[str, Any], step: dict[str, Any]) -> bool:
225
+ result = audit.get("result", {}) if isinstance(audit.get("result"), dict) else {}
226
+ source = audit.get("source", {}) if isinstance(audit.get("source"), dict) else {}
227
+ policy_checks = result.get("policy_checks", [])
228
+ risks = result.get("risks", [])
229
+ if not isinstance(policy_checks, list):
230
+ policy_checks = []
231
+ if not isinstance(risks, list):
232
+ risks = []
233
+
234
+ return (
235
+ str(audit.get("task_id", "")) == str(step.get("task_id", ""))
236
+ and int(source.get("step_number", 0) or 0) == int(step.get("step_number", 0) or 0)
237
+ and str(audit.get("mode", "")) != "dry-run"
238
+ and "dry_run_only" not in {str(item) for item in policy_checks}
239
+ and "execution_not_performed" not in {str(item) for item in risks}
240
+ and bool(result.get("success"))
241
+ and str(result.get("outcome", "")) == "done"
242
+ )
243
+
244
+
245
+ def route_feature(slug: str) -> dict[str, Any]:
246
+ discovered = discover_flow_directory("feature", slug, "auto")
247
+ if not discovered:
248
+ return {
249
+ "success": False,
250
+ "flow": "feature",
251
+ "slug": slug,
252
+ "recommended_command": "cgc-plan",
253
+ "reason": "Feature 工作流目录尚不存在。",
254
+ "next": "先初始化该 feature 的工作流骨架。",
255
+ }
256
+ artifact_class, directory = discovered
257
+
258
+ design = find_artifact_file(directory, "-design.md")
259
+ checklist = find_artifact_file(directory, "-checklist.yaml")
260
+ acceptance = find_artifact_file(directory, "-acceptance.md")
261
+
262
+ if not design or not checklist or not acceptance:
263
+ return {
264
+ "success": False,
265
+ "flow": "feature",
266
+ "slug": slug,
267
+ "artifact_class": artifact_class,
268
+ "directory": str(directory),
269
+ "recommended_command": "cgc-plan",
270
+ "reason": "当前功能开发工作流骨架不完整。",
271
+ "next": "补齐或修复缺失的功能开发产物文件。",
272
+ }
273
+
274
+ if not checklist_has_codecgc_step(checklist):
275
+ return {
276
+ "success": False,
277
+ "flow": "feature",
278
+ "slug": slug,
279
+ "artifact_class": artifact_class,
280
+ "directory": str(directory),
281
+ "recommended_command": "cgc-plan",
282
+ "reason": "当前功能开发工作流已存在,但还不可执行。",
283
+ "next": "继续细化设计,并补上有效的 CodeCGC 步骤契约。",
284
+ }
285
+
286
+ if checklist_has_planning_step(checklist):
287
+ return {
288
+ "success": False,
289
+ "flow": "feature",
290
+ "slug": slug,
291
+ "artifact_class": artifact_class,
292
+ "directory": str(directory),
293
+ "recommended_command": "cgc-plan",
294
+ "reason": "当前功能开发清单里仍然存在仅规划用途的拆分或路由确认步骤。",
295
+ "next": "先完成必须的拆分或路由澄清,再执行剩余的限定范围步骤。",
296
+ }
297
+
298
+ blocked_first_step, first_step = first_pending_step_is_not_executable(checklist)
299
+ if blocked_first_step:
300
+ result = {
301
+ "success": False,
302
+ "flow": "feature",
303
+ "slug": slug,
304
+ "artifact_class": artifact_class,
305
+ "directory": str(directory),
306
+ "recommended_command": "cgc-plan",
307
+ "reason": "当前功能开发清单仍以不可执行的占位步骤开头。",
308
+ "next": "先澄清范围、归属和目标路径,再尝试执行该功能开发工作流。",
309
+ "current_step": {
310
+ "step_number": int(first_step.get("step_number", 0) or 0),
311
+ "task_id": str(first_step.get("task_id", "")),
312
+ "kind": str(first_step.get("kind", "")),
313
+ "target_paths": first_step.get("target_paths", []),
314
+ "task_summary": str(first_step.get("task_summary", "")),
315
+ },
316
+ }
317
+ split_payload = build_route_scope_split_payload(first_step or {})
318
+ if split_payload:
319
+ result["reason"] = "当前功能开发步骤混合了前后端或 shared 范围,必须先拆分后才能执行。"
320
+ result["next"] = "先回到 cgc-plan,按拆分建议把当前步骤改写成纯 frontend 或纯 backend 步骤,再继续执行。"
321
+ result.update(split_payload)
322
+ return result
323
+
324
+ try:
325
+ next_step = select_next_executable_step(checklist)
326
+ except Exception:
327
+ next_step = None
328
+
329
+ review = extract_review_metadata(acceptance)
330
+
331
+ if next_step is None:
332
+ if review.get("decision") == "accepted":
333
+ return {
334
+ "success": True,
335
+ "flow": "feature",
336
+ "slug": slug,
337
+ "artifact_class": artifact_class,
338
+ "directory": str(directory),
339
+ "review": review,
340
+ "current_step": None,
341
+ "audit_path": "",
342
+ "recommended_command": "",
343
+ "reason": "当前功能开发工作流已通过审核,且没有剩余待执行步骤。",
344
+ "next": "当前功能开发工作流已关闭,除非后续新增新的跟进步骤。",
345
+ }
346
+ return {
347
+ "success": True,
348
+ "flow": "feature",
349
+ "slug": slug,
350
+ "artifact_class": artifact_class,
351
+ "directory": str(directory),
352
+ "review": review,
353
+ "current_step": None,
354
+ "audit_path": "",
355
+ "recommended_command": "",
356
+ "reason": "当前功能开发工作流已没有剩余待执行步骤。",
357
+ "next": "请检查该工作流是否应关闭,或是否需要规划新的跟进步骤。",
358
+ }
359
+
360
+ audit_path, audit = find_audit_for_task_id(str(next_step.get("task_id", "")), artifact_class)
361
+ review_for_current_step = review_matches_step(review, next_step)
362
+ current_step = {
363
+ "step_number": int(next_step.get("step_number", 0) or 0),
364
+ "task_id": str(next_step.get("task_id", "")),
365
+ "kind": str(next_step.get("kind", "")),
366
+ "target_paths": next_step.get("target_paths", []),
367
+ "task_summary": str(next_step.get("task_summary", "")),
368
+ }
369
+ if is_test_codecgc_block(
370
+ {
371
+ "kind": current_step["kind"],
372
+ "task_id": current_step["task_id"],
373
+ "task_summary": current_step["task_summary"],
374
+ "target_paths": current_step["target_paths"],
375
+ "step_type": "test" if "-test-step-" in current_step["task_id"] else "",
376
+ }
377
+ ):
378
+ return {
379
+ "success": True,
380
+ "flow": "feature",
381
+ "slug": slug,
382
+ "artifact_class": artifact_class,
383
+ "directory": str(directory),
384
+ "review": review,
385
+ "current_step": current_step,
386
+ "audit_path": str(audit_path) if audit_path else "",
387
+ "recommended_command": "cgc-test",
388
+ "reason": "当前功能开发工作流正在等待测试步骤执行。",
389
+ "next": "执行当前测试步骤。",
390
+ }
391
+
392
+ if review_for_current_step and review.get("decision") == "changes-requested":
393
+ return {
394
+ "success": True,
395
+ "flow": "feature",
396
+ "slug": slug,
397
+ "artifact_class": artifact_class,
398
+ "directory": str(directory),
399
+ "review": review,
400
+ "current_step": current_step,
401
+ "audit_path": str(audit_path) if audit_path else "",
402
+ "recommended_command": "cgc-build",
403
+ "reason": "当前功能开发步骤在审核后仍需继续补充修改。",
404
+ "next": "完成要求的后续修改后,重新执行当前功能开发步骤。",
405
+ }
406
+
407
+ if audit:
408
+ if audit_is_ready_for_review(audit, next_step):
409
+ return {
410
+ "success": True,
411
+ "flow": "feature",
412
+ "slug": slug,
413
+ "artifact_class": artifact_class,
414
+ "directory": str(directory),
415
+ "review": review,
416
+ "current_step": current_step,
417
+ "audit_path": str(audit_path) if audit_path else "",
418
+ "recommended_command": "cgc-review",
419
+ "reason": "当前功能开发步骤已有执行证据,但还没有对应的审核结论。",
420
+ "next": f"审核最新审计产物 {audit_path},并回写验收结论。",
421
+ }
422
+ return {
423
+ "success": True,
424
+ "flow": "feature",
425
+ "slug": slug,
426
+ "artifact_class": artifact_class,
427
+ "directory": str(directory),
428
+ "review": review,
429
+ "current_step": current_step,
430
+ "audit_path": str(audit_path) if audit_path else "",
431
+ "recommended_command": "cgc-build",
432
+ "reason": "当前功能开发步骤还没有“通过”审核结论。",
433
+ "next": "执行或继续推进当前待执行的功能开发步骤。",
434
+ }
435
+
436
+ return {
437
+ "success": True,
438
+ "flow": "feature",
439
+ "slug": slug,
440
+ "artifact_class": artifact_class,
441
+ "directory": str(directory),
442
+ "review": review,
443
+ "current_step": current_step,
444
+ "audit_path": "",
445
+ "recommended_command": "cgc-build",
446
+ "reason": "当前功能开发清单已包含可执行步骤元数据,但还没有执行审计。",
447
+ "next": "执行当前功能开发步骤。",
448
+ }
449
+
450
+
451
+ def route_issue(slug: str) -> dict[str, Any]:
452
+ discovered = discover_flow_directory("issue", slug, "auto")
453
+ if not discovered:
454
+ return {
455
+ "success": False,
456
+ "flow": "issue",
457
+ "slug": slug,
458
+ "recommended_command": "cgc-plan",
459
+ "reason": "Issue 工作流目录尚不存在。",
460
+ "next": "先初始化该 issue 的工作流骨架。",
461
+ }
462
+ artifact_class, directory = discovered
463
+
464
+ report = find_artifact_file(directory, "-report.md")
465
+ analysis = find_artifact_file(directory, "-analysis.md")
466
+ fix = find_artifact_file(directory, "-fix.yaml")
467
+ fix_note = find_artifact_file(directory, "-fix-note.md")
468
+
469
+ if not report or not analysis or not fix or not fix_note:
470
+ return {
471
+ "success": False,
472
+ "flow": "issue",
473
+ "slug": slug,
474
+ "artifact_class": artifact_class,
475
+ "directory": str(directory),
476
+ "recommended_command": "cgc-plan",
477
+ "reason": "Issue 工作流骨架不完整。",
478
+ "next": "补齐或修复缺失的 issue 产物文件。",
479
+ }
480
+
481
+ if not checklist_has_codecgc_step(fix):
482
+ return {
483
+ "success": False,
484
+ "flow": "issue",
485
+ "slug": slug,
486
+ "artifact_class": artifact_class,
487
+ "directory": str(directory),
488
+ "recommended_command": "cgc-plan",
489
+ "reason": "当前问题修复工作流已存在,但还不可执行。",
490
+ "next": "继续细化修复范围,并补上有效的 CodeCGC 步骤契约。",
491
+ }
492
+
493
+ if checklist_has_planning_step(fix):
494
+ return {
495
+ "success": False,
496
+ "flow": "issue",
497
+ "slug": slug,
498
+ "artifact_class": artifact_class,
499
+ "directory": str(directory),
500
+ "recommended_command": "cgc-plan",
501
+ "reason": "当前问题修复清单里仍然存在仅规划用途的拆分或路由确认步骤。",
502
+ "next": "先完成必须的拆分或路由澄清,再执行剩余的限定范围修复步骤。",
503
+ }
504
+
505
+ blocked_first_step, first_step = first_pending_step_is_not_executable(fix)
506
+ if blocked_first_step:
507
+ result = {
508
+ "success": False,
509
+ "flow": "issue",
510
+ "slug": slug,
511
+ "artifact_class": artifact_class,
512
+ "directory": str(directory),
513
+ "recommended_command": "cgc-plan",
514
+ "reason": "当前问题修复清单仍以不可执行的占位步骤开头。",
515
+ "next": "先澄清修复范围、归属和目标路径,再尝试执行该问题修复工作流。",
516
+ "current_step": {
517
+ "step_number": int(first_step.get("step_number", 0) or 0),
518
+ "task_id": str(first_step.get("task_id", "")),
519
+ "kind": str(first_step.get("kind", "")),
520
+ "target_paths": first_step.get("target_paths", []),
521
+ "task_summary": str(first_step.get("task_summary", "")),
522
+ },
523
+ }
524
+ split_payload = build_route_scope_split_payload(first_step or {})
525
+ if split_payload:
526
+ result["reason"] = "当前问题修复步骤混合了前后端或 shared 范围,必须先拆分后才能执行。"
527
+ result["next"] = "先回到 cgc-plan,按拆分建议把当前修复步骤改写成纯 frontend 或纯 backend 步骤,再继续执行。"
528
+ result.update(split_payload)
529
+ return result
530
+
531
+ try:
532
+ next_step = select_next_executable_step(fix)
533
+ except Exception:
534
+ next_step = None
535
+
536
+ review = extract_review_metadata(fix_note)
537
+
538
+ if next_step is None:
539
+ if review.get("decision") == "accepted":
540
+ return {
541
+ "success": True,
542
+ "flow": "issue",
543
+ "slug": slug,
544
+ "artifact_class": artifact_class,
545
+ "directory": str(directory),
546
+ "review": review,
547
+ "current_step": None,
548
+ "audit_path": "",
549
+ "recommended_command": "",
550
+ "reason": "当前问题修复工作流已通过审核,且没有剩余待执行步骤。",
551
+ "next": "当前问题修复工作流已关闭,除非后续新增新的跟进修复步骤。",
552
+ }
553
+ return {
554
+ "success": True,
555
+ "flow": "issue",
556
+ "slug": slug,
557
+ "artifact_class": artifact_class,
558
+ "directory": str(directory),
559
+ "review": review,
560
+ "current_step": None,
561
+ "audit_path": "",
562
+ "recommended_command": "",
563
+ "reason": "当前问题修复工作流已没有剩余待执行步骤。",
564
+ "next": "请检查该工作流是否应关闭,或是否需要规划新的跟进修复步骤。",
565
+ }
566
+
567
+ audit_path, audit = find_audit_for_task_id(str(next_step.get("task_id", "")), artifact_class)
568
+ review_for_current_step = review_matches_step(review, next_step)
569
+ current_step = {
570
+ "step_number": int(next_step.get("step_number", 0) or 0),
571
+ "task_id": str(next_step.get("task_id", "")),
572
+ "kind": str(next_step.get("kind", "")),
573
+ "target_paths": next_step.get("target_paths", []),
574
+ "task_summary": str(next_step.get("task_summary", "")),
575
+ }
576
+ if is_test_codecgc_block(
577
+ {
578
+ "kind": current_step["kind"],
579
+ "task_id": current_step["task_id"],
580
+ "task_summary": current_step["task_summary"],
581
+ "target_paths": current_step["target_paths"],
582
+ "step_type": "test" if "-test-step-" in current_step["task_id"] else "",
583
+ }
584
+ ):
585
+ return {
586
+ "success": True,
587
+ "flow": "issue",
588
+ "slug": slug,
589
+ "artifact_class": artifact_class,
590
+ "directory": str(directory),
591
+ "review": review,
592
+ "current_step": current_step,
593
+ "audit_path": str(audit_path) if audit_path else "",
594
+ "recommended_command": "cgc-test",
595
+ "reason": "当前问题修复工作流正在等待测试步骤执行。",
596
+ "next": "执行当前测试步骤。",
597
+ }
598
+
599
+ if review_for_current_step and review.get("decision") == "changes-requested":
600
+ return {
601
+ "success": True,
602
+ "flow": "issue",
603
+ "slug": slug,
604
+ "artifact_class": artifact_class,
605
+ "directory": str(directory),
606
+ "review": review,
607
+ "current_step": current_step,
608
+ "audit_path": str(audit_path) if audit_path else "",
609
+ "recommended_command": "cgc-fix",
610
+ "reason": "当前问题修复步骤在审核后仍需继续补充修改。",
611
+ "next": "完成要求的后续修复后,重新执行当前问题修复步骤。",
612
+ }
613
+
614
+ if audit:
615
+ if audit_is_ready_for_review(audit, next_step):
616
+ return {
617
+ "success": True,
618
+ "flow": "issue",
619
+ "slug": slug,
620
+ "artifact_class": artifact_class,
621
+ "directory": str(directory),
622
+ "review": review,
623
+ "current_step": current_step,
624
+ "audit_path": str(audit_path) if audit_path else "",
625
+ "recommended_command": "cgc-review",
626
+ "reason": "当前问题修复步骤已有执行证据,但还没有对应的审核结论。",
627
+ "next": f"审核最新审计产物 {audit_path},并回写修复结论。",
628
+ }
629
+ return {
630
+ "success": True,
631
+ "flow": "issue",
632
+ "slug": slug,
633
+ "artifact_class": artifact_class,
634
+ "directory": str(directory),
635
+ "review": review,
636
+ "current_step": current_step,
637
+ "audit_path": str(audit_path) if audit_path else "",
638
+ "recommended_command": "cgc-fix",
639
+ "reason": "当前问题修复步骤还没有“通过”审核结论。",
640
+ "next": "执行或继续推进当前待执行的问题修复步骤。",
641
+ }
642
+
643
+ return {
644
+ "success": True,
645
+ "flow": "issue",
646
+ "slug": slug,
647
+ "artifact_class": artifact_class,
648
+ "directory": str(directory),
649
+ "review": review,
650
+ "current_step": current_step,
651
+ "audit_path": "",
652
+ "recommended_command": "cgc-fix",
653
+ "reason": "当前问题修复产物已包含可执行步骤元数据,但还没有执行审计。",
654
+ "next": "执行当前问题修复步骤。",
655
+ }
656
+
657
+
658
+ def main() -> int:
659
+ configure_utf8_stdio()
660
+ parser = build_parser()
661
+ args = parser.parse_args()
662
+
663
+ result = route_feature(args.slug) if args.flow == "feature" else route_issue(args.slug)
664
+ result["recommended_command"] = to_public_command(result.get("recommended_command", ""))
665
+ result = attach_route_summary(result)
666
+ print_json(result)
667
+ return 0
668
+
669
+
670
+ if __name__ == "__main__":
671
+ raise SystemExit(main())