@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,410 @@
1
+ import argparse
2
+ import asyncio
3
+ import datetime
4
+ import json
5
+ import sys
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from codecgc_artifact_roots import execution_root
10
+ from codecgc_artifact_roots import normalize_artifact_class
11
+ from codecgc_console_io import configure_utf8_stdio
12
+ from codecgc_console_io import print_json
13
+ from codecgc_executor_registry import build_executor_env
14
+ from codecgc_executor_registry import get_executor_config
15
+ from codecgc_file_evidence import snapshot_workspace
16
+ from codecgc_file_evidence import verify_workspace_changes
17
+ from codecgc_runtime_paths import PACKAGE_ROOT
18
+ from codecgc_runtime_paths import PROJECT_ROOT
19
+ from mcp import ClientSession
20
+ from mcp.client.stdio import StdioServerParameters, stdio_client
21
+
22
+ from build_codecgc_task import build_parser as build_task_parser
23
+ from build_codecgc_task import build_tool_call, load_simple_routing_config
24
+
25
+
26
+ WORKSPACE = PACKAGE_ROOT
27
+ PROJECT_WORKSPACE = PROJECT_ROOT
28
+
29
+
30
+ def extract_split_payload(text: str) -> dict[str, Any]:
31
+ marker = "SPLIT_PAYLOAD:"
32
+ if marker not in text:
33
+ return {}
34
+ _, _, suffix = text.partition(marker)
35
+ payload_text = suffix.strip()
36
+ if not payload_text:
37
+ return {}
38
+ try:
39
+ parsed = json.loads(payload_text)
40
+ except Exception:
41
+ return {}
42
+ return parsed if isinstance(parsed, dict) else {}
43
+
44
+
45
+ def strip_split_payload_marker(text: str) -> str:
46
+ marker = "SPLIT_PAYLOAD:"
47
+ if marker not in text:
48
+ return text.strip()
49
+ prefix, _, _ = text.partition(marker)
50
+ return prefix.strip()
51
+
52
+
53
+ def build_server_params(target: str) -> StdioServerParameters:
54
+ config = get_executor_config(target)
55
+ return StdioServerParameters(
56
+ command=str(config["python_command"]),
57
+ args=["-m", str(config["python_module"])],
58
+ env=build_executor_env(target),
59
+ )
60
+
61
+
62
+ def build_parser() -> argparse.ArgumentParser:
63
+ parser = build_task_parser()
64
+ parser.description = "Build, execute, and audit a CodeCGC MCP task."
65
+ parser.add_argument(
66
+ "--timeout-seconds",
67
+ type=int,
68
+ default=120,
69
+ help="MCP call timeout in seconds.",
70
+ )
71
+ parser.add_argument(
72
+ "--dry-run",
73
+ action="store_true",
74
+ help="Only print the built task payload without executing it.",
75
+ )
76
+ parser.add_argument(
77
+ "--audit-root",
78
+ default="",
79
+ help="Optional directory used to store CodeCGC execution audit files. Defaults to the artifact-class-specific execution root.",
80
+ )
81
+ parser.add_argument(
82
+ "--skip-audit-write",
83
+ action="store_true",
84
+ help="Do not write execution audit artifacts.",
85
+ )
86
+ return parser
87
+
88
+
89
+ def extract_text_content(value: Any) -> str:
90
+ if isinstance(value, str):
91
+ return value
92
+ if isinstance(value, list):
93
+ parts: list[str] = []
94
+ for item in value:
95
+ if isinstance(item, str):
96
+ parts.append(item)
97
+ continue
98
+ if isinstance(item, dict):
99
+ text = item.get("text")
100
+ if isinstance(text, str):
101
+ parts.append(text)
102
+ return "\n".join(part for part in parts if part).strip()
103
+ return ""
104
+
105
+
106
+ def normalize_mcp_result(raw: dict[str, Any]) -> dict[str, Any]:
107
+ structured_content = raw.get("structuredContent")
108
+ if isinstance(structured_content, dict):
109
+ return structured_content
110
+
111
+ content = raw.get("content")
112
+ if isinstance(content, list):
113
+ for item in content:
114
+ if not isinstance(item, dict):
115
+ continue
116
+ if item.get("type") != "text":
117
+ continue
118
+ text = item.get("text")
119
+ if not isinstance(text, str):
120
+ continue
121
+ try:
122
+ parsed = json.loads(text)
123
+ except json.JSONDecodeError:
124
+ continue
125
+ if isinstance(parsed, dict):
126
+ return parsed
127
+
128
+ return {
129
+ "success": False,
130
+ "error": "Unable to parse structured MCP tool result.",
131
+ "raw_content": content,
132
+ }
133
+
134
+
135
+ def sanitize_for_json(value: Any, seen: set[int] | None = None) -> Any:
136
+ if seen is None:
137
+ seen = set()
138
+
139
+ if isinstance(value, (str, int, float, bool)) or value is None:
140
+ return value
141
+
142
+ object_id = id(value)
143
+ if object_id in seen:
144
+ return "[circular]"
145
+
146
+ if isinstance(value, dict):
147
+ seen.add(object_id)
148
+ sanitized = {str(key): sanitize_for_json(item, seen) for key, item in value.items()}
149
+ seen.remove(object_id)
150
+ return sanitized
151
+
152
+ if isinstance(value, list):
153
+ seen.add(object_id)
154
+ sanitized = [sanitize_for_json(item, seen) for item in value]
155
+ seen.remove(object_id)
156
+ return sanitized
157
+
158
+ if isinstance(value, tuple):
159
+ seen.add(object_id)
160
+ sanitized = [sanitize_for_json(item, seen) for item in value]
161
+ seen.remove(object_id)
162
+ return sanitized
163
+
164
+ return str(value)
165
+
166
+
167
+ def classify_outcome(result: dict[str, Any]) -> str:
168
+ if result.get("success"):
169
+ return "done"
170
+
171
+ error_text = str(result.get("error", "")).lower()
172
+ summary_text = str(result.get("summary", "")).lower()
173
+ combined = f"{error_text}\n{summary_text}"
174
+ target_missing_markers = [
175
+ "target file path does not exist",
176
+ "target directory path does not exist",
177
+ "does not exist in the current workspace",
178
+ "current workspace",
179
+ "当前工作区中不存在目标文件",
180
+ "目标文件路径在当前工作区下不存在",
181
+ "在当前工作区里不存在",
182
+ "工作区里没有",
183
+ "目标文件路径在当前工作区里对不上",
184
+ "目标文件缺失",
185
+ "没有可实施对象",
186
+ "目标文件本身缺失",
187
+ "提供正确文件路径",
188
+ "恢复到工作区",
189
+ ]
190
+ if "shared paths" in error_text or "mixed frontend/backend" in error_text:
191
+ return "split-required"
192
+ if "not covered by model-routing.yaml" in error_text or "unsupported kind" in error_text:
193
+ return "design-gap"
194
+ if any(marker in combined for marker in target_missing_markers):
195
+ return "design-gap"
196
+ if "does not exist" in error_text or "timeout" in error_text:
197
+ return "blocked"
198
+ if "unable to parse structured mcp tool result" in error_text:
199
+ return "executor-failure"
200
+ return "executor-failure"
201
+
202
+
203
+ def build_audit_record(
204
+ *,
205
+ payload: dict[str, Any],
206
+ args: argparse.Namespace,
207
+ result: dict[str, Any],
208
+ mode: str,
209
+ file_evidence: dict[str, Any] | None = None,
210
+ ) -> dict[str, Any]:
211
+ tool_args = payload.get("tool_args", {})
212
+ target_paths = tool_args.get("target_paths", [])
213
+ task_id = str(tool_args.get("task_id", "unknown-task"))
214
+ reported_changed_files = result.get("changed_files", [])
215
+ if not isinstance(reported_changed_files, list):
216
+ reported_changed_files = []
217
+
218
+ return {
219
+ "product": "CodeCGC",
220
+ "version": 1,
221
+ "mode": mode,
222
+ "timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
223
+ "task_id": task_id,
224
+ "target": payload.get("target"),
225
+ "tool_name": payload.get("tool_name"),
226
+ "target_paths": target_paths,
227
+ "route_notes": payload.get("route_notes", []),
228
+ "routing_file": payload.get("routing_file"),
229
+ "source": payload.get("source"),
230
+ "task_summary": tool_args.get("task_summary", ""),
231
+ "constraints": tool_args.get("constraints", []),
232
+ "acceptance_criteria": tool_args.get("acceptance_criteria", []),
233
+ "cd": tool_args.get("cd", ""),
234
+ "requested_session_id": tool_args.get("SESSION_ID", ""),
235
+ "result": {
236
+ "success": bool(result.get("success")),
237
+ "outcome": classify_outcome(result),
238
+ "task_id": result.get("task_id", task_id),
239
+ "session_id": result.get("SESSION_ID", ""),
240
+ "summary": result.get("summary", ""),
241
+ "changed_files": reported_changed_files,
242
+ "policy_checks": result.get("policy_checks", []),
243
+ "risks": result.get("risks", []),
244
+ "error": result.get("error", ""),
245
+ "split_suggestion": result.get("split_suggestion", {}),
246
+ },
247
+ "file_evidence": file_evidence or {
248
+ "evidence_source": "not-collected",
249
+ "workspace_changed_files": [],
250
+ "verified_changed_files": [],
251
+ "out_of_scope_changed_files": [],
252
+ "file_diffs": [],
253
+ "evidence_confidence": "unknown",
254
+ "git_evidence": {
255
+ "git_repository_detected": False,
256
+ "git_root": "",
257
+ "history_available": False,
258
+ "status": "not-collected",
259
+ "tracked_changed_files": [],
260
+ "untracked_changed_files": [],
261
+ "git_changed_files": [],
262
+ },
263
+ },
264
+ }
265
+
266
+
267
+ def write_audit_file(audit_root: Path, audit_record: dict[str, Any]) -> Path:
268
+ audit_root.mkdir(parents=True, exist_ok=True)
269
+
270
+ task_id = str(audit_record.get("task_id", "unknown-task"))
271
+ safe_task_id = "".join(ch if ch.isalnum() or ch in {"-", "_"} else "_" for ch in task_id)
272
+ filename = f"{safe_task_id}.json"
273
+ audit_path = audit_root / filename
274
+ audit_path.write_text(json.dumps(audit_record, ensure_ascii=False, indent=2), encoding="utf-8")
275
+ return audit_path
276
+
277
+
278
+ async def execute_payload(payload: dict[str, Any], timeout_seconds: int) -> dict[str, Any]:
279
+ params = build_server_params(payload["target"])
280
+
281
+ async with stdio_client(params) as (read_stream, write_stream):
282
+ async with ClientSession(read_stream, write_stream) as session:
283
+ await session.initialize()
284
+ raw_result = await session.call_tool(
285
+ payload["tool_name"],
286
+ payload["tool_args"],
287
+ read_timeout_seconds=datetime.timedelta(seconds=timeout_seconds),
288
+ )
289
+
290
+ dumped = raw_result.model_dump(mode="json")
291
+ normalized = normalize_mcp_result(dumped)
292
+ normalized["raw_mcp_result"] = dumped
293
+ return normalized
294
+
295
+
296
+ async def run_task(args: argparse.Namespace) -> dict[str, Any]:
297
+ routing = load_simple_routing_config(Path(args.routing_file))
298
+ try:
299
+ payload = build_tool_call(args, routing)
300
+ except Exception as error:
301
+ message = str(error)
302
+ lowered = message.lower()
303
+ outcome = "executor-failure"
304
+ split_payload: dict[str, Any] = {}
305
+ if "not covered by model-routing.yaml" in lowered or "unsupported kind" in lowered:
306
+ outcome = "design-gap"
307
+ elif "shared paths" in lowered or "mixed frontend/backend" in lowered:
308
+ outcome = "split-required"
309
+ split_payload = extract_split_payload(message)
310
+ elif "does not exist" in lowered or "timeout" in lowered:
311
+ outcome = "blocked"
312
+ cleaned_message = strip_split_payload_marker(message)
313
+ return {
314
+ "success": False,
315
+ "mode": "build-failed",
316
+ "payload": {},
317
+ "result": {
318
+ "success": False,
319
+ "outcome": outcome,
320
+ "task_id": "",
321
+ "session_id": "",
322
+ "summary": cleaned_message,
323
+ "changed_files": [],
324
+ "policy_checks": [],
325
+ "risks": [],
326
+ "error": cleaned_message,
327
+ "split_suggestion": split_payload,
328
+ },
329
+ "audit": {
330
+ "written": False,
331
+ "path": "",
332
+ },
333
+ }
334
+ source = payload.get("source", {})
335
+ artifact_class = normalize_artifact_class(source.get("artifact_class", "product") if isinstance(source, dict) else "product")
336
+ file_evidence: dict[str, Any] | None = None
337
+
338
+ if args.dry_run:
339
+ task_result = {
340
+ "success": True,
341
+ "task_id": payload["tool_args"].get("task_id", ""),
342
+ "summary": "Dry run only. Task payload built but not executed.",
343
+ "changed_files": payload["tool_args"].get("target_paths", []),
344
+ "policy_checks": ["dry_run_only"],
345
+ "risks": ["execution_not_performed"],
346
+ "SESSION_ID": payload["tool_args"].get("SESSION_ID", ""),
347
+ }
348
+ mode = "dry-run"
349
+ else:
350
+ step_timeout = int(payload.get("timeout_seconds", 0)) or 0
351
+ effective_timeout = step_timeout if step_timeout > 0 else args.timeout_seconds
352
+ before_snapshot = snapshot_workspace(PROJECT_WORKSPACE)
353
+ task_result = await execute_payload(payload, effective_timeout)
354
+ after_snapshot = snapshot_workspace(PROJECT_WORKSPACE)
355
+ file_evidence = verify_workspace_changes(
356
+ before_snapshot,
357
+ after_snapshot,
358
+ list(payload["tool_args"].get("target_paths", [])),
359
+ )
360
+ mode = "execute"
361
+
362
+ audit_record = build_audit_record(
363
+ payload=payload,
364
+ args=args,
365
+ result=task_result,
366
+ mode=mode,
367
+ file_evidence=file_evidence,
368
+ )
369
+
370
+ audit_path = None
371
+ if not args.skip_audit_write:
372
+ audit_root = Path(args.audit_root) if args.audit_root else execution_root(artifact_class)
373
+ audit_path = write_audit_file(audit_root, audit_record)
374
+
375
+ output = {
376
+ "success": bool(task_result.get("success")),
377
+ "mode": mode,
378
+ "payload": payload,
379
+ "result": task_result,
380
+ "audit": {
381
+ "written": not args.skip_audit_write,
382
+ "path": str(audit_path) if audit_path else "",
383
+ },
384
+ }
385
+ return sanitize_for_json(output)
386
+
387
+
388
+ def main() -> int:
389
+ configure_utf8_stdio()
390
+ parser = build_parser()
391
+ args = parser.parse_args()
392
+
393
+ try:
394
+ result = asyncio.run(run_task(args))
395
+ except Exception as error:
396
+ print_json(
397
+ {
398
+ "success": False,
399
+ "error": str(error),
400
+ },
401
+ file=sys.stderr,
402
+ )
403
+ return 1
404
+
405
+ print_json(result)
406
+ return 0
407
+
408
+
409
+ if __name__ == "__main__":
410
+ raise SystemExit(main())
@@ -0,0 +1,105 @@
1
+ import argparse
2
+ import sys
3
+
4
+ from codecgc_command_surface import matches_command
5
+ from codecgc_command_surface import to_public_command
6
+ from codecgc_console_io import configure_utf8_stdio
7
+ from codecgc_console_io import print_json
8
+ from codecgc_flow_control import build_execution_result
9
+ from codecgc_flow_control import build_not_ready_result
10
+ from codecgc_session_recovery import resolve_session_id_from_task
11
+ from codecgc_step_control import get_step_metadata
12
+ from codecgc_step_control import select_next_executable_step
13
+ from run_codecgc_flow_step import resolve_checklist_path
14
+ from codecgc_workflow_runtime import run_json_script
15
+
16
+
17
+ def build_parser() -> argparse.ArgumentParser:
18
+ parser = argparse.ArgumentParser(
19
+ description="CodeCGC 的高层测试执行入口:校验路由与步骤状态,并执行一个测试步骤。"
20
+ )
21
+ parser.add_argument("--flow", required=True, choices=["feature", "issue"])
22
+ parser.add_argument("--slug", required=True)
23
+ parser.add_argument("--step-number", type=int)
24
+ parser.add_argument("--checklist-file", default="")
25
+ parser.add_argument("--audit-root", default="")
26
+ parser.add_argument("--timeout-seconds", type=int, default=120)
27
+ parser.add_argument("--session-id", default="")
28
+ parser.add_argument("--dry-run", action="store_true")
29
+ parser.add_argument("--return-all-messages", action="store_true")
30
+ return parser
31
+
32
+
33
+ def main() -> int:
34
+ configure_utf8_stdio()
35
+ parser = build_parser()
36
+ args = parser.parse_args()
37
+
38
+ route = run_json_script("route_codecgc_workflow.py", "--flow", args.flow, "--slug", args.slug)
39
+ if not matches_command(str(route.get("recommended_command", "")).strip(), "cgc-test"):
40
+ result = build_not_ready_result(args.flow, args.slug, route, "cgc-test")
41
+ print_json(result)
42
+ return 1
43
+
44
+ try:
45
+ checklist_path = resolve_checklist_path(args.flow, args.slug, args.checklist_file)
46
+ selected = (
47
+ get_step_metadata(checklist_path, args.step_number)
48
+ if args.step_number
49
+ else select_next_executable_step(checklist_path)
50
+ )
51
+ except Exception as error:
52
+ result = {
53
+ "success": False,
54
+ "flow": args.flow,
55
+ "slug": args.slug,
56
+ "state": "not-ready",
57
+ "failure_type": "workflow-state",
58
+ "route": route,
59
+ "error": str(error),
60
+ "recommended_command": to_public_command("cgc-plan"),
61
+ "next": "先处理仅规划步骤,或补齐缺失的测试步骤契约,再进入 test 执行。",
62
+ }
63
+ print_json(result)
64
+ return 1
65
+
66
+ effective_session_id = args.session_id.strip()
67
+ if not effective_session_id:
68
+ effective_session_id = resolve_session_id_from_task(
69
+ str(selected.get("task_id", "")).strip(),
70
+ str(route.get("artifact_class", "")).strip() or "product",
71
+ )
72
+
73
+ command_args = [
74
+ "--flow",
75
+ args.flow,
76
+ "--slug",
77
+ args.slug,
78
+ "--step-number",
79
+ str(selected["step_number"]),
80
+ "--timeout-seconds",
81
+ str(args.timeout_seconds),
82
+ ]
83
+ if args.checklist_file:
84
+ command_args.extend(["--checklist-file", args.checklist_file])
85
+ if args.audit_root:
86
+ command_args.extend(["--audit-root", args.audit_root])
87
+ if effective_session_id:
88
+ command_args.extend(["--session-id", effective_session_id])
89
+ if args.dry_run:
90
+ command_args.append("--dry-run")
91
+ if args.return_all_messages:
92
+ command_args.append("--return-all-messages")
93
+
94
+ execution = run_json_script("run_codecgc_flow_step.py", *command_args)
95
+ result = build_execution_result(flow=args.flow, slug=args.slug, route=route, execution=execution)
96
+ result["selected_step"] = selected
97
+ result["reused_session_id"] = effective_session_id
98
+ print_json(result)
99
+ if result.get("success") or str(result.get("state", "")).strip() == "executed-dry-run":
100
+ return 0
101
+ return 1
102
+
103
+
104
+ if __name__ == "__main__":
105
+ raise SystemExit(main())
@@ -0,0 +1,41 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ from codecgc_executor_registry import build_executor_registry
5
+
6
+
7
+ WORKSPACE = Path(__file__).resolve().parents[1]
8
+ MCP_CONFIG_PATH = WORKSPACE / ".mcp.json"
9
+
10
+
11
+ def build_mcp_config() -> dict:
12
+ registry = build_executor_registry()
13
+ servers: dict[str, dict] = {}
14
+
15
+ for config in registry.values():
16
+ servers[str(config["mcp_server_name"])] = {
17
+ "command": str(config["python_command"]),
18
+ "args": ["-m", str(config["python_module"])],
19
+ "env": {
20
+ "PYTHONPATH": str(config["pythonpath"]),
21
+ },
22
+ }
23
+
24
+ return {"mcpServers": servers}
25
+
26
+
27
+ def write_mcp_config(target_path: Path) -> Path:
28
+ config = build_mcp_config()
29
+ target_path.parent.mkdir(parents=True, exist_ok=True)
30
+ target_path.write_text(json.dumps(config, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
31
+ return target_path
32
+
33
+
34
+ def main() -> int:
35
+ write_mcp_config(MCP_CONFIG_PATH)
36
+ print(json.dumps({"success": True, "path": str(MCP_CONFIG_PATH)}, ensure_ascii=False, indent=2))
37
+ return 0
38
+
39
+
40
+ if __name__ == "__main__":
41
+ raise SystemExit(main())
@@ -0,0 +1,78 @@
1
+ import argparse
2
+ from pathlib import Path
3
+
4
+ from codecgc_artifact_roots import artifact_output_root
5
+ from codecgc_console_io import configure_utf8_stdio
6
+ from codecgc_console_io import print_json
7
+ from codecgc_governance_dedupe import has_existing_governance_entry
8
+
9
+
10
+ SECTION_HEADING = "## 9. Governance Updates"
11
+
12
+
13
+ def resolve_architecture_path() -> Path:
14
+ return artifact_output_root("product") / "architecture" / "codecgc-system-map.md"
15
+
16
+
17
+ def build_parser() -> argparse.ArgumentParser:
18
+ parser = argparse.ArgumentParser(
19
+ description="把一条长期有效的架构治理更新写入 codecgc/architecture/codecgc-system-map.md。"
20
+ )
21
+ parser.add_argument("--summary", required=True, help="一行架构更新摘要。")
22
+ parser.add_argument("--note", default="", help="可选的当前态架构说明。")
23
+ parser.add_argument("--source", default="", help="可选的来源产物、工作流或讨论引用。")
24
+ return parser
25
+
26
+
27
+ def ensure_governance_section(text: str) -> str:
28
+ if SECTION_HEADING in text:
29
+ return text
30
+ stripped = text.rstrip()
31
+ suffix = "\n\n" if stripped else ""
32
+ return f"{stripped}{suffix}{SECTION_HEADING}\n\n"
33
+
34
+
35
+ def has_existing_entry(existing: str, summary: str) -> bool:
36
+ return has_existing_governance_entry(existing, summary, field_labels=["Summary", "摘要"])
37
+
38
+
39
+ def append_architecture_update(path: Path, summary: str, note: str, source: str) -> dict[str, str]:
40
+ existing = path.read_text(encoding="utf-8")
41
+ ensured = ensure_governance_section(existing)
42
+ cleaned_summary = summary.strip()
43
+ if has_existing_entry(ensured, cleaned_summary):
44
+ return {
45
+ "path": str(path),
46
+ "summary": cleaned_summary,
47
+ "note": note.strip(),
48
+ "source": source.strip(),
49
+ "created": "false",
50
+ }
51
+
52
+ lines = [f"### {cleaned_summary}", "", f"- 摘要: {cleaned_summary}"]
53
+ if note.strip():
54
+ lines.append(f"- 当前状态说明: {note.strip()}")
55
+ if source.strip():
56
+ lines.append(f"- 来源: {source.strip()}")
57
+ lines.append("")
58
+
59
+ path.write_text(ensured + "\n".join(lines), encoding="utf-8")
60
+ return {
61
+ "path": str(path),
62
+ "summary": cleaned_summary,
63
+ "note": note.strip(),
64
+ "source": source.strip(),
65
+ "created": "true",
66
+ }
67
+
68
+
69
+ def main() -> int:
70
+ configure_utf8_stdio()
71
+ args = build_parser().parse_args()
72
+ result = append_architecture_update(resolve_architecture_path(), args.summary, args.note, args.source)
73
+ print_json({"success": True, "architecture": result})
74
+ return 0
75
+
76
+
77
+ if __name__ == "__main__":
78
+ raise SystemExit(main())