@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,276 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from codecgc_console_io import render_summary_block
|
|
7
|
+
from codecgc_executor_registry import build_executor_registry
|
|
8
|
+
from codecgc_runtime_paths import resolve_workspace_root
|
|
9
|
+
|
|
10
|
+
WORKSPACE = Path(__file__).resolve().parents[1]
|
|
11
|
+
REGISTRY_PATH = WORKSPACE / "codecgc" / "reference" / "external-capability-registry.json"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def load_registry() -> dict[str, Any]:
|
|
15
|
+
return json.loads(REGISTRY_PATH.read_text(encoding="utf-8"))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_workspace_mcp_servers(workspace_override: str = "") -> tuple[dict[str, Any], Path]:
|
|
19
|
+
workspace_root = resolve_workspace_root(workspace_override)
|
|
20
|
+
mcp_path = workspace_root / ".mcp.json"
|
|
21
|
+
if not mcp_path.exists():
|
|
22
|
+
return {}, workspace_root
|
|
23
|
+
try:
|
|
24
|
+
payload = json.loads(mcp_path.read_text(encoding="utf-8"))
|
|
25
|
+
except Exception:
|
|
26
|
+
return {}, workspace_root
|
|
27
|
+
servers = payload.get("mcpServers", {})
|
|
28
|
+
return servers if isinstance(servers, dict) else {}, workspace_root
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def probe_executor_capability(entry: dict[str, Any]) -> dict[str, Any]:
|
|
32
|
+
target = str(entry.get("executor_target", "")).strip()
|
|
33
|
+
server_names = [str(item).strip() for item in entry.get("mcp_server_names", []) if str(item).strip()]
|
|
34
|
+
registry = build_executor_registry()
|
|
35
|
+
config = registry.get(target, {})
|
|
36
|
+
python_module = Path(str(config.get("pythonpath", ""))) / Path(str(config.get("python_module", "")).replace(".", "/")).with_suffix(".py")
|
|
37
|
+
server_name = str(config.get("mcp_server_name", "")).strip()
|
|
38
|
+
path_ready = python_module.exists()
|
|
39
|
+
server_ready = server_name in server_names if server_name else False
|
|
40
|
+
return {
|
|
41
|
+
"local_ready": bool(config) and path_ready and server_ready,
|
|
42
|
+
"observed_servers": [server_name] if server_name else [],
|
|
43
|
+
"details": {
|
|
44
|
+
"executor_target": target,
|
|
45
|
+
"python_module_path": str(python_module),
|
|
46
|
+
"path_ready": path_ready,
|
|
47
|
+
"server_ready": server_ready,
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def probe_workspace_mcp_capability(entry: dict[str, Any], workspace_servers: dict[str, Any]) -> dict[str, Any]:
|
|
53
|
+
server_names = [str(item).strip() for item in entry.get("mcp_server_names", []) if str(item).strip()]
|
|
54
|
+
observed = [name for name in server_names if name in workspace_servers]
|
|
55
|
+
return {
|
|
56
|
+
"local_ready": bool(observed),
|
|
57
|
+
"observed_servers": observed,
|
|
58
|
+
"details": {
|
|
59
|
+
"declared_servers": server_names,
|
|
60
|
+
"observed_servers": observed,
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def normalize_capability_entry(entry: dict[str, Any], workspace_servers: dict[str, Any]) -> dict[str, Any]:
|
|
66
|
+
capability_id = str(entry.get("id", "")).strip()
|
|
67
|
+
name = str(entry.get("name", capability_id)).strip()
|
|
68
|
+
status = str(entry.get("status", "")).strip()
|
|
69
|
+
category = str(entry.get("category", "")).strip()
|
|
70
|
+
integration_type = str(entry.get("integration_type", "")).strip()
|
|
71
|
+
required = bool(entry.get("required", False))
|
|
72
|
+
probe_kind = str(entry.get("probe_kind", "")).strip()
|
|
73
|
+
|
|
74
|
+
if probe_kind == "executor":
|
|
75
|
+
probe = probe_executor_capability(entry)
|
|
76
|
+
else:
|
|
77
|
+
probe = probe_workspace_mcp_capability(entry, workspace_servers)
|
|
78
|
+
|
|
79
|
+
policy_ready = all(
|
|
80
|
+
[
|
|
81
|
+
capability_id,
|
|
82
|
+
name,
|
|
83
|
+
status,
|
|
84
|
+
category,
|
|
85
|
+
integration_type,
|
|
86
|
+
]
|
|
87
|
+
)
|
|
88
|
+
blocking = required and status == "integrated" and not probe["local_ready"]
|
|
89
|
+
drift = status != "integrated" and bool(probe["observed_servers"])
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
"id": capability_id,
|
|
93
|
+
"name": name,
|
|
94
|
+
"status": status,
|
|
95
|
+
"category": category,
|
|
96
|
+
"integration_type": integration_type,
|
|
97
|
+
"required": required,
|
|
98
|
+
"owner": str(entry.get("owner", "")).strip(),
|
|
99
|
+
"description": str(entry.get("description", "")).strip(),
|
|
100
|
+
"mcp_server_names": [str(item).strip() for item in entry.get("mcp_server_names", []) if str(item).strip()],
|
|
101
|
+
"local_ready": probe["local_ready"],
|
|
102
|
+
"observed_servers": probe["observed_servers"],
|
|
103
|
+
"policy_ready": policy_ready,
|
|
104
|
+
"blocking": blocking,
|
|
105
|
+
"drift": drift,
|
|
106
|
+
"details": probe["details"],
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def audit_external_capabilities(workspace_override: str = "") -> dict[str, Any]:
|
|
111
|
+
registry = load_registry()
|
|
112
|
+
workspace_servers, workspace_root = load_workspace_mcp_servers(workspace_override)
|
|
113
|
+
entries = registry.get("capabilities", [])
|
|
114
|
+
normalized: list[dict[str, Any]] = []
|
|
115
|
+
malformed_count = 0
|
|
116
|
+
blocking_items: list[dict[str, Any]] = []
|
|
117
|
+
drift_items: list[dict[str, Any]] = []
|
|
118
|
+
|
|
119
|
+
for raw_entry in entries:
|
|
120
|
+
if not isinstance(raw_entry, dict):
|
|
121
|
+
malformed_count += 1
|
|
122
|
+
continue
|
|
123
|
+
item = normalize_capability_entry(raw_entry, workspace_servers)
|
|
124
|
+
normalized.append(item)
|
|
125
|
+
if not item["policy_ready"]:
|
|
126
|
+
malformed_count += 1
|
|
127
|
+
if item["blocking"]:
|
|
128
|
+
blocking_items.append(item)
|
|
129
|
+
if item["drift"]:
|
|
130
|
+
drift_items.append(item)
|
|
131
|
+
|
|
132
|
+
counts = {
|
|
133
|
+
"integrated": sum(1 for item in normalized if item["status"] == "integrated"),
|
|
134
|
+
"planned": sum(1 for item in normalized if item["status"] == "planned"),
|
|
135
|
+
"optional": sum(1 for item in normalized if item["status"] == "optional"),
|
|
136
|
+
}
|
|
137
|
+
ready = malformed_count == 0 and not blocking_items
|
|
138
|
+
human_summary = "外部能力接入清单已就绪。"
|
|
139
|
+
if blocking_items:
|
|
140
|
+
human_summary = "必需外部能力存在未接通或未观测到的阻塞项。"
|
|
141
|
+
elif malformed_count > 0:
|
|
142
|
+
human_summary = "外部能力登记表存在缺失字段或非法项。"
|
|
143
|
+
elif drift_items:
|
|
144
|
+
human_summary = "外部能力登记表已就绪,但发现本地额外接入漂移。"
|
|
145
|
+
|
|
146
|
+
recommended_next_action = ""
|
|
147
|
+
if blocking_items:
|
|
148
|
+
blocking_ids = {str(item["id"]) for item in blocking_items}
|
|
149
|
+
if "codexmcp" in blocking_ids or "geminimcp" in blocking_ids:
|
|
150
|
+
recommended_next_action = "先运行 cgc-install 或 cgc-doctor,修复必需执行器的项目级集成与运行前置。"
|
|
151
|
+
else:
|
|
152
|
+
recommended_next_action = "先补齐缺失的必需外部能力注册,再继续后续流程。"
|
|
153
|
+
elif malformed_count > 0:
|
|
154
|
+
recommended_next_action = "先修复 codecgc/reference/external-capability-registry.json 中缺失字段或非法项。"
|
|
155
|
+
elif drift_items:
|
|
156
|
+
recommended_next_action = "检查本地 .mcp.json 是否引入了登记表之外的额外接入,并决定是登记还是移除。"
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
"success": ready,
|
|
160
|
+
"mode": "external-capability-audit",
|
|
161
|
+
"workspace": str(workspace_root),
|
|
162
|
+
"registry_path": str(REGISTRY_PATH),
|
|
163
|
+
"summary": {
|
|
164
|
+
"ready": ready,
|
|
165
|
+
"scope": "外部能力白名单、接入状态声明与本地 MCP 观测状态",
|
|
166
|
+
"human_summary": human_summary,
|
|
167
|
+
"capability_count": len(normalized),
|
|
168
|
+
"integrated_count": counts["integrated"],
|
|
169
|
+
"planned_count": counts["planned"],
|
|
170
|
+
"optional_count": counts["optional"],
|
|
171
|
+
"malformed_count": malformed_count,
|
|
172
|
+
"blocking_count": len(blocking_items),
|
|
173
|
+
"drift_count": len(drift_items),
|
|
174
|
+
"recommended_next_action": recommended_next_action,
|
|
175
|
+
},
|
|
176
|
+
"capabilities": normalized,
|
|
177
|
+
"workspace_mcp_servers": sorted(str(name) for name in workspace_servers.keys()),
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def build_summary(result: dict[str, Any]) -> str:
|
|
182
|
+
summary = result.get("summary", {}) if isinstance(result.get("summary"), dict) else {}
|
|
183
|
+
lines = [
|
|
184
|
+
f"- 工作区: {result.get('workspace', '')}",
|
|
185
|
+
f"- 登记表: {result.get('registry_path', '')}",
|
|
186
|
+
f"- 范围: {summary.get('scope', '')}",
|
|
187
|
+
f"- 就绪: {'是' if summary.get('ready') else '否'}",
|
|
188
|
+
f"- 摘要: {summary.get('human_summary', '')}",
|
|
189
|
+
f"- 能力总数: {summary.get('capability_count', 0)}",
|
|
190
|
+
f"- 已集成: {summary.get('integrated_count', 0)}",
|
|
191
|
+
f"- 规划中: {summary.get('planned_count', 0)}",
|
|
192
|
+
f"- 可选项: {summary.get('optional_count', 0)}",
|
|
193
|
+
f"- 阻塞项: {summary.get('blocking_count', 0)}",
|
|
194
|
+
f"- 漂移项: {summary.get('drift_count', 0)}",
|
|
195
|
+
]
|
|
196
|
+
for item in result.get("capabilities", []):
|
|
197
|
+
if not isinstance(item, dict):
|
|
198
|
+
continue
|
|
199
|
+
optional_text = "可选" if not item.get("required") else "必需"
|
|
200
|
+
lines.append(
|
|
201
|
+
"- 能力: "
|
|
202
|
+
+ f"{item.get('id', '')} "
|
|
203
|
+
+ f"[{item.get('status', '')}/{item.get('category', '')}/{optional_text}] "
|
|
204
|
+
+ f"本地={'已观测' if item.get('local_ready') else '未观测'} "
|
|
205
|
+
+ f"服务器={', '.join(item.get('observed_servers', [])) or '无'}"
|
|
206
|
+
)
|
|
207
|
+
next_action = str(summary.get("recommended_next_action", "")).strip()
|
|
208
|
+
next_actions = [next_action] if next_action else []
|
|
209
|
+
memos_item = next(
|
|
210
|
+
(
|
|
211
|
+
item
|
|
212
|
+
for item in result.get("capabilities", [])
|
|
213
|
+
if isinstance(item, dict) and str(item.get("id", "")).strip() == "memos"
|
|
214
|
+
),
|
|
215
|
+
None,
|
|
216
|
+
)
|
|
217
|
+
if isinstance(memos_item, dict) and not memos_item.get("local_ready"):
|
|
218
|
+
next_actions.append("如需跨会话记忆,可先在 Claude 中配置官方 memos-mcp,再重新执行 cgc-external-audit")
|
|
219
|
+
augment_item = next(
|
|
220
|
+
(
|
|
221
|
+
item
|
|
222
|
+
for item in result.get("capabilities", [])
|
|
223
|
+
if isinstance(item, dict) and str(item.get("id", "")).strip() == "augment-search"
|
|
224
|
+
),
|
|
225
|
+
None,
|
|
226
|
+
)
|
|
227
|
+
if isinstance(augment_item, dict) and not augment_item.get("local_ready"):
|
|
228
|
+
next_actions.append("如需代码检索增强,可先在 Claude 中配置 ace-tool MCP,再重新执行 cgc-external-audit")
|
|
229
|
+
github_item = next(
|
|
230
|
+
(
|
|
231
|
+
item
|
|
232
|
+
for item in result.get("capabilities", [])
|
|
233
|
+
if isinstance(item, dict) and str(item.get("id", "")).strip() == "github-mcp"
|
|
234
|
+
),
|
|
235
|
+
None,
|
|
236
|
+
)
|
|
237
|
+
if isinstance(github_item, dict) and not github_item.get("local_ready"):
|
|
238
|
+
next_actions.append("如需 GitHub 仓库、PR 与 issue 协作,可先在 Claude 中配置官方 github MCP,再重新执行 cgc-external-audit")
|
|
239
|
+
linear_item = next(
|
|
240
|
+
(
|
|
241
|
+
item
|
|
242
|
+
for item in result.get("capabilities", [])
|
|
243
|
+
if isinstance(item, dict) and str(item.get("id", "")).strip() == "linear-mcp"
|
|
244
|
+
),
|
|
245
|
+
None,
|
|
246
|
+
)
|
|
247
|
+
if isinstance(linear_item, dict) and not linear_item.get("local_ready"):
|
|
248
|
+
next_actions.append("如需任务与项目节奏协作,可先在 Claude 中配置官方 Linear MCP,再重新执行 cgc-external-audit")
|
|
249
|
+
return render_summary_block("CodeCGC 外部能力审计", lines, next_actions)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def main() -> int:
|
|
253
|
+
parser = argparse.ArgumentParser(description="Audit CodeCGC external capability registry and local MCP observations.")
|
|
254
|
+
parser.add_argument(
|
|
255
|
+
"--format",
|
|
256
|
+
choices=["json", "summary"],
|
|
257
|
+
default="summary",
|
|
258
|
+
help="Output format. Summary is the default product-facing mode; use json for debugging or automation.",
|
|
259
|
+
)
|
|
260
|
+
parser.add_argument(
|
|
261
|
+
"--workspace",
|
|
262
|
+
default="",
|
|
263
|
+
help="Optional target workspace root. Defaults to the current shell workspace.",
|
|
264
|
+
)
|
|
265
|
+
args = parser.parse_args()
|
|
266
|
+
|
|
267
|
+
result = audit_external_capabilities(args.workspace)
|
|
268
|
+
if args.format == "summary":
|
|
269
|
+
print(build_summary(result))
|
|
270
|
+
else:
|
|
271
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
272
|
+
return 0 if result.get("success") else 1
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
if __name__ == "__main__":
|
|
276
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from codecgc_artifact_roots import discover_flow_directory
|
|
7
|
+
from codecgc_artifact_roots import execution_root
|
|
8
|
+
from codecgc_artifact_roots import normalize_artifact_class
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
WORKSPACE = Path(__file__).resolve().parents[1]
|
|
12
|
+
PRODUCT_EXECUTION_ROOT = execution_root("product")
|
|
13
|
+
FIXTURE_EXECUTION_ROOT = execution_root("fixture")
|
|
14
|
+
OLD_REPO_NAME = "CodeCCG"
|
|
15
|
+
LEGACY_DEMO_TASK_IDS = {"audit-dry-run-001", "audit-dry-run-002"}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_json(path: Path) -> dict[str, Any] | None:
|
|
19
|
+
try:
|
|
20
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
21
|
+
except Exception:
|
|
22
|
+
return None
|
|
23
|
+
return data if isinstance(data, dict) else None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def detect_root_class(path: Path) -> str:
|
|
27
|
+
if path.parent.resolve() == FIXTURE_EXECUTION_ROOT.resolve():
|
|
28
|
+
return "fixture"
|
|
29
|
+
return "product"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def contains_old_repo_name(value: Any) -> bool:
|
|
33
|
+
return isinstance(value, str) and OLD_REPO_NAME in value
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def expected_artifact_filename(artifact_type: str, artifact_slug: str) -> str:
|
|
37
|
+
base_slug = artifact_slug[11:] if len(artifact_slug) > 11 and artifact_slug[4] == "-" else artifact_slug
|
|
38
|
+
if artifact_type == "feature":
|
|
39
|
+
return f"{base_slug}-acceptance.md"
|
|
40
|
+
if artifact_type == "issue":
|
|
41
|
+
return f"{base_slug}-fix-note.md"
|
|
42
|
+
return ""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def validate_source_contract(source: dict[str, Any]) -> list[dict[str, str]]:
|
|
46
|
+
issues: list[dict[str, str]] = []
|
|
47
|
+
artifact_type = str(source.get("artifact_type", "")).strip()
|
|
48
|
+
artifact_slug = str(source.get("artifact_slug", "")).strip()
|
|
49
|
+
artifact_class = normalize_artifact_class(str(source.get("artifact_class", "product")))
|
|
50
|
+
artifact_file = str(source.get("artifact_file", "")).strip()
|
|
51
|
+
|
|
52
|
+
if not artifact_type or not artifact_slug:
|
|
53
|
+
issues.append(
|
|
54
|
+
{
|
|
55
|
+
"problem": "source-missing-artifact-identity",
|
|
56
|
+
"detail": "artifact_type or artifact_slug is missing.",
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
return issues
|
|
60
|
+
|
|
61
|
+
if artifact_type not in {"feature", "issue"}:
|
|
62
|
+
issues.append(
|
|
63
|
+
{
|
|
64
|
+
"problem": "source-unsupported-artifact-type",
|
|
65
|
+
"detail": f"artifact_type={artifact_type}",
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
return issues
|
|
69
|
+
|
|
70
|
+
discovered = discover_flow_directory(artifact_type, artifact_slug, artifact_class)
|
|
71
|
+
if not discovered:
|
|
72
|
+
issues.append(
|
|
73
|
+
{
|
|
74
|
+
"problem": "source-artifact-directory-missing",
|
|
75
|
+
"detail": f"artifact_type={artifact_type}, artifact_slug={artifact_slug}, artifact_class={artifact_class}",
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
return issues
|
|
79
|
+
|
|
80
|
+
discovered_class, directory = discovered
|
|
81
|
+
if discovered_class != artifact_class:
|
|
82
|
+
issues.append(
|
|
83
|
+
{
|
|
84
|
+
"problem": "artifact-class-directory-mismatch",
|
|
85
|
+
"detail": f"artifact_class={artifact_class}, actual_directory_class={discovered_class}",
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
expected_name = expected_artifact_filename(artifact_type, artifact_slug)
|
|
90
|
+
expected_artifact_path = directory / expected_name if expected_name else None
|
|
91
|
+
if expected_artifact_path and not expected_artifact_path.exists():
|
|
92
|
+
issues.append(
|
|
93
|
+
{
|
|
94
|
+
"problem": "artifact-target-missing",
|
|
95
|
+
"detail": str(expected_artifact_path),
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if artifact_file:
|
|
100
|
+
artifact_file_path = Path(artifact_file)
|
|
101
|
+
if not artifact_file_path.exists():
|
|
102
|
+
issues.append(
|
|
103
|
+
{
|
|
104
|
+
"problem": "source-artifact-file-missing",
|
|
105
|
+
"detail": artifact_file,
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
elif directory not in artifact_file_path.parents:
|
|
109
|
+
issues.append(
|
|
110
|
+
{
|
|
111
|
+
"problem": "source-artifact-file-directory-mismatch",
|
|
112
|
+
"detail": f"artifact_file={artifact_file}, expected_directory={directory}",
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return issues
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def inspect_audit(path: Path) -> list[dict[str, str]]:
|
|
120
|
+
issues: list[dict[str, str]] = []
|
|
121
|
+
data = load_json(path)
|
|
122
|
+
if not data:
|
|
123
|
+
return [
|
|
124
|
+
{
|
|
125
|
+
"path": str(path),
|
|
126
|
+
"problem": "invalid-json",
|
|
127
|
+
"detail": "Audit file is not a valid JSON object.",
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
source = data.get("source", {}) if isinstance(data.get("source"), dict) else {}
|
|
132
|
+
task_id = str(data.get("task_id", "")).strip()
|
|
133
|
+
if task_id in LEGACY_DEMO_TASK_IDS:
|
|
134
|
+
return [
|
|
135
|
+
{
|
|
136
|
+
"path": str(path),
|
|
137
|
+
"problem": "legacy-demo-audit",
|
|
138
|
+
"detail": "历史演示用审计样本缺少工作流产物身份信息,应归档或从发布就绪的执行历史中排除。",
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
artifact_class = normalize_artifact_class(str(source.get("artifact_class", "product")))
|
|
142
|
+
root_class = detect_root_class(path)
|
|
143
|
+
if artifact_class != root_class:
|
|
144
|
+
issues.append(
|
|
145
|
+
{
|
|
146
|
+
"path": str(path),
|
|
147
|
+
"problem": "artifact-class-root-mismatch",
|
|
148
|
+
"detail": f"artifact_class={artifact_class}, execution_root={root_class}",
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if contains_old_repo_name(data.get("routing_file")):
|
|
153
|
+
issues.append(
|
|
154
|
+
{
|
|
155
|
+
"path": str(path),
|
|
156
|
+
"problem": "old-repo-name-routing-file",
|
|
157
|
+
"detail": str(data.get("routing_file", "")),
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
if contains_old_repo_name(data.get("cd")):
|
|
161
|
+
issues.append(
|
|
162
|
+
{
|
|
163
|
+
"path": str(path),
|
|
164
|
+
"problem": "old-repo-name-cd",
|
|
165
|
+
"detail": str(data.get("cd", "")),
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
if contains_old_repo_name(source.get("artifact_file")):
|
|
169
|
+
issues.append(
|
|
170
|
+
{
|
|
171
|
+
"path": str(path),
|
|
172
|
+
"problem": "old-repo-name-artifact-file",
|
|
173
|
+
"detail": str(source.get("artifact_file", "")),
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
for item in validate_source_contract(source):
|
|
178
|
+
issues.append(
|
|
179
|
+
{
|
|
180
|
+
"path": str(path),
|
|
181
|
+
"problem": str(item.get("problem", "")),
|
|
182
|
+
"detail": str(item.get("detail", "")),
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return issues
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def collect_issues() -> dict[str, Any]:
|
|
190
|
+
scanned = 0
|
|
191
|
+
issues: list[dict[str, str]] = []
|
|
192
|
+
for root in (PRODUCT_EXECUTION_ROOT, FIXTURE_EXECUTION_ROOT):
|
|
193
|
+
if not root.exists():
|
|
194
|
+
continue
|
|
195
|
+
for path in sorted(root.glob("*.json")):
|
|
196
|
+
scanned += 1
|
|
197
|
+
issues.extend(inspect_audit(path))
|
|
198
|
+
return {
|
|
199
|
+
"success": len(issues) == 0,
|
|
200
|
+
"scanned": scanned,
|
|
201
|
+
"issue_count": len(issues),
|
|
202
|
+
"issues": issues,
|
|
203
|
+
"product_execution_root": str(PRODUCT_EXECUTION_ROOT),
|
|
204
|
+
"fixture_execution_root": str(FIXTURE_EXECUTION_ROOT),
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def build_summary(result: dict[str, Any]) -> str:
|
|
209
|
+
lines = [
|
|
210
|
+
"CodeCGC 历史执行审计一致性检查",
|
|
211
|
+
f"- 扫描文件数: {result.get('scanned', 0)}",
|
|
212
|
+
f"- 问题数: {result.get('issue_count', 0)}",
|
|
213
|
+
f"- 就绪: {'是' if result.get('success') else '否'}",
|
|
214
|
+
]
|
|
215
|
+
for item in result.get("issues", []):
|
|
216
|
+
if not isinstance(item, dict):
|
|
217
|
+
continue
|
|
218
|
+
lines.append(
|
|
219
|
+
"- 问题: "
|
|
220
|
+
+ f"{item.get('path', '')} "
|
|
221
|
+
+ f"[{item.get('problem', '')}] {item.get('detail', '')}"
|
|
222
|
+
)
|
|
223
|
+
return "\n".join(lines)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def main() -> int:
|
|
227
|
+
parser = argparse.ArgumentParser(
|
|
228
|
+
description="检查历史 CodeCGC 执行审计中是否存在放置错误、旧仓库名残留或来源契约不一致问题。"
|
|
229
|
+
)
|
|
230
|
+
parser.add_argument("--format", choices=["json", "summary"], default="json")
|
|
231
|
+
args = parser.parse_args()
|
|
232
|
+
|
|
233
|
+
result = collect_issues()
|
|
234
|
+
if args.format == "summary":
|
|
235
|
+
print(build_summary(result))
|
|
236
|
+
else:
|
|
237
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
238
|
+
return 0 if result["success"] else 1
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
if __name__ == "__main__":
|
|
242
|
+
raise SystemExit(main())
|