@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.
- package/.claude/hooks/route-edit.ps1 +86 -0
- package/INSTALLATION.md +550 -0
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/bin/cgc-build.js +4 -0
- package/bin/cgc-doctor.js +4 -0
- package/bin/cgc-entry.js +4 -0
- package/bin/cgc-external-audit.js +4 -0
- package/bin/cgc-fix.js +4 -0
- package/bin/cgc-history.js +4 -0
- package/bin/cgc-install.js +4 -0
- package/bin/cgc-lifecycle.js +4 -0
- package/bin/cgc-package-audit.js +4 -0
- package/bin/cgc-plan.js +4 -0
- package/bin/cgc-release-readiness.js +4 -0
- package/bin/cgc-review.js +4 -0
- package/bin/cgc-route.js +4 -0
- package/bin/cgc-status.js +4 -0
- package/bin/cgc-test.js +4 -0
- package/bin/cgc.js +4 -0
- package/bin/codecgc.js +1284 -0
- package/codecgc/cgc/SKILL.md +46 -0
- package/codecgc/cgc-arch/SKILL.md +61 -0
- package/codecgc/cgc-build/SKILL.md +53 -0
- package/codecgc/cgc-decide/SKILL.md +55 -0
- package/codecgc/cgc-fix/SKILL.md +47 -0
- package/codecgc/cgc-learn/SKILL.md +46 -0
- package/codecgc/cgc-onboard/SKILL.md +52 -0
- package/codecgc/cgc-plan/SKILL.md +48 -0
- package/codecgc/cgc-refactor/SKILL.md +46 -0
- package/codecgc/cgc-req/SKILL.md +61 -0
- package/codecgc/cgc-review/SKILL.md +57 -0
- package/codecgc/cgc-roadmap/SKILL.md +55 -0
- package/codecgc/cgc-test/SKILL.md +21 -0
- package/codecgc/reference/api-cgc-review-libdoc.md +13 -0
- package/codecgc/reference/artifact-class-policy.md +81 -0
- package/codecgc/reference/build-flow.md +95 -0
- package/codecgc/reference/checklist-contract.md +103 -0
- package/codecgc/reference/execution-audit.md +121 -0
- package/codecgc/reference/execution-model.md +118 -0
- package/codecgc/reference/execution-routing.md +130 -0
- package/codecgc/reference/executor-contract.md +87 -0
- package/codecgc/reference/external-capability-registry.json +104 -0
- package/codecgc/reference/fix-flow.md +94 -0
- package/codecgc/reference/fixture-governance.md +60 -0
- package/codecgc/reference/flow-execution.md +65 -0
- package/codecgc/reference/lifecycle-map.md +172 -0
- package/codecgc/reference/lifecycle-playbook.md +104 -0
- package/codecgc/reference/long-lived-artifacts.md +98 -0
- package/codecgc/reference/operation-guide.md +242 -0
- package/codecgc/reference/release-maintenance-playbook.md +150 -0
- package/codecgc/reference/review-writeback.md +141 -0
- package/codecgc/reference/role-model.md +128 -0
- package/codecgc/reference/runtime-boundary.md +72 -0
- package/codecgc/reference/shared-conventions.md +93 -0
- package/codecgc/reference/workflow-scaffold.md +57 -0
- package/codexmcp/LICENSE +21 -0
- package/codexmcp/README.md +294 -0
- package/codexmcp/pyproject.toml +37 -0
- package/codexmcp/src/codexmcp/__init__.py +4 -0
- package/codexmcp/src/codexmcp/cli.py +12 -0
- package/codexmcp/src/codexmcp/server.py +529 -0
- package/geminimcp/README.md +258 -0
- package/geminimcp/pyproject.toml +15 -0
- package/geminimcp/src/geminimcp/__init__.py +4 -0
- package/geminimcp/src/geminimcp/cli.py +12 -0
- package/geminimcp/src/geminimcp/server.py +465 -0
- package/model-routing.yaml +30 -0
- package/package.json +90 -0
- package/requirements.txt +1 -0
- package/scripts/README-codecgc-cli.md +89 -0
- package/scripts/audit_codecgc_external_capabilities.py +276 -0
- package/scripts/audit_codecgc_historical_audits.py +242 -0
- package/scripts/audit_codecgc_lifecycle.py +241 -0
- package/scripts/audit_codecgc_package_runtime.py +445 -0
- package/scripts/audit_codecgc_release_readiness.py +202 -0
- package/scripts/audit_codecgc_review_policy.py +82 -0
- package/scripts/audit_codecgc_workflow_history.py +317 -0
- package/scripts/build_codecgc_task.py +487 -0
- package/scripts/codecgc_artifact_roots.py +40 -0
- package/scripts/codecgc_cli.py +843 -0
- package/scripts/codecgc_command_surface.py +28 -0
- package/scripts/codecgc_console_io.py +45 -0
- package/scripts/codecgc_executor_registry.py +54 -0
- package/scripts/codecgc_file_evidence.py +349 -0
- package/scripts/codecgc_flow_control.py +233 -0
- package/scripts/codecgc_governance_dedupe.py +161 -0
- package/scripts/codecgc_plan_decision.py +103 -0
- package/scripts/codecgc_review_control.py +588 -0
- package/scripts/codecgc_roadmap_templates.py +149 -0
- package/scripts/codecgc_routing_paths.py +16 -0
- package/scripts/codecgc_routing_template.py +135 -0
- package/scripts/codecgc_runtime_paths.py +22 -0
- package/scripts/codecgc_session_recovery.py +44 -0
- package/scripts/codecgc_step_control.py +154 -0
- package/scripts/codecgc_workflow_runtime.py +63 -0
- package/scripts/codecgc_workflow_templates.py +437 -0
- package/scripts/entry_codecgc_workflow.py +3419 -0
- package/scripts/exercise_mcp_tools.py +109 -0
- package/scripts/expand_codecgc_roadmap.py +664 -0
- package/scripts/init_codecgc_roadmap.py +134 -0
- package/scripts/init_codecgc_workflow.py +207 -0
- package/scripts/install_codecgc.py +938 -0
- package/scripts/migrate_demo_workflows_to_fixtures.py +128 -0
- package/scripts/normalize_codecgc_audits.py +114 -0
- package/scripts/normalize_codecgc_governance_docs.py +79 -0
- package/scripts/normalize_codecgc_workflow_docs.py +269 -0
- package/scripts/plan_codecgc_workflow.py +970 -0
- package/scripts/refresh_codecgc_review_policy.py +223 -0
- package/scripts/review_codecgc_workflow.py +88 -0
- package/scripts/route_codecgc_workflow.py +671 -0
- package/scripts/run_codecgc_build.py +104 -0
- package/scripts/run_codecgc_fix.py +104 -0
- package/scripts/run_codecgc_flow_step.py +165 -0
- package/scripts/run_codecgc_task.py +410 -0
- package/scripts/run_codecgc_test.py +105 -0
- package/scripts/sync_codecgc_mcp_config.py +41 -0
- package/scripts/write_codecgc_architecture.py +78 -0
- package/scripts/write_codecgc_decision.py +83 -0
- package/scripts/write_codecgc_explore.py +118 -0
- package/scripts/write_codecgc_guide.py +141 -0
- package/scripts/write_codecgc_learning.py +87 -0
- package/scripts/write_codecgc_libdoc.py +140 -0
- package/scripts/write_codecgc_refactor.py +78 -0
- package/scripts/write_codecgc_requirement.py +78 -0
- package/scripts/write_codecgc_review.py +291 -0
- package/scripts/write_codecgc_roadmap.py +122 -0
- 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())
|