@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,202 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from audit_codecgc_external_capabilities import audit_external_capabilities
|
|
7
|
+
from audit_codecgc_package_runtime import audit_package_runtime
|
|
8
|
+
from codecgc_console_io import render_summary_block
|
|
9
|
+
from install_codecgc import collect_doctor_status
|
|
10
|
+
from install_codecgc import collect_install_status
|
|
11
|
+
|
|
12
|
+
WORKSPACE = Path(__file__).resolve().parents[1]
|
|
13
|
+
|
|
14
|
+
LIFECYCLE_REQUIRED_PATHS = [
|
|
15
|
+
"codecgc/reference/lifecycle-map.md",
|
|
16
|
+
"codecgc/reference/lifecycle-playbook.md",
|
|
17
|
+
"codecgc/reference/operation-guide.md",
|
|
18
|
+
"codecgc/reference/release-maintenance-playbook.md",
|
|
19
|
+
"codecgc/reference/external-capability-registry.json",
|
|
20
|
+
"codecgc/compound/codecgc-capability-matrix.md",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
ROADMAP_REQUIRED_PATHS = [
|
|
24
|
+
"codecgc/roadmap/codecgc-release-maintenance/overview.md",
|
|
25
|
+
"codecgc/roadmap/codecgc-release-maintenance/phases.md",
|
|
26
|
+
"codecgc/roadmap/codecgc-release-maintenance/delivery-plan.md",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
DEPLOY_SIGNAL_GLOBS = {
|
|
30
|
+
"github_actions": [".github/workflows/*.yml", ".github/workflows/*.yaml"],
|
|
31
|
+
"dockerfile": ["Dockerfile", "**/Dockerfile"],
|
|
32
|
+
"docker_compose": ["docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"],
|
|
33
|
+
"deploy_scripts": ["deploy/*.sh", "deploy/*.ps1", "scripts/deploy*.sh", "scripts/deploy*.ps1", "scripts/release*.sh", "scripts/release*.ps1"],
|
|
34
|
+
"runtime_env_examples": [".env.example", ".env.*.example", "**/.env.example"],
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def audit_document_set(paths: list[str]) -> dict[str, Any]:
|
|
39
|
+
existing: list[str] = []
|
|
40
|
+
missing: list[str] = []
|
|
41
|
+
for item in paths:
|
|
42
|
+
if (WORKSPACE / item).exists():
|
|
43
|
+
existing.append(item)
|
|
44
|
+
else:
|
|
45
|
+
missing.append(item)
|
|
46
|
+
return {
|
|
47
|
+
"required_count": len(paths),
|
|
48
|
+
"existing": existing,
|
|
49
|
+
"missing": missing,
|
|
50
|
+
"ready": len(missing) == 0,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def collect_deploy_signals() -> dict[str, Any]:
|
|
55
|
+
signal_hits: dict[str, list[str]] = {}
|
|
56
|
+
total_hits = 0
|
|
57
|
+
for signal_name, patterns in DEPLOY_SIGNAL_GLOBS.items():
|
|
58
|
+
matched: list[str] = []
|
|
59
|
+
seen: set[str] = set()
|
|
60
|
+
for pattern in patterns:
|
|
61
|
+
for path in WORKSPACE.glob(pattern):
|
|
62
|
+
if not path.is_file():
|
|
63
|
+
continue
|
|
64
|
+
relative = path.relative_to(WORKSPACE).as_posix()
|
|
65
|
+
if relative in seen:
|
|
66
|
+
continue
|
|
67
|
+
seen.add(relative)
|
|
68
|
+
matched.append(relative)
|
|
69
|
+
signal_hits[signal_name] = sorted(matched)
|
|
70
|
+
total_hits += len(matched)
|
|
71
|
+
|
|
72
|
+
if total_hits == 0:
|
|
73
|
+
stage = "none"
|
|
74
|
+
elif signal_hits["github_actions"] or signal_hits["dockerfile"] or signal_hits["docker_compose"]:
|
|
75
|
+
stage = "repo-release-ready"
|
|
76
|
+
else:
|
|
77
|
+
stage = "basic-signals"
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
"deploy_signals_detected": signal_hits,
|
|
81
|
+
"total_detected": total_hits,
|
|
82
|
+
"deploy_readiness_stage": stage,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def audit_release_readiness(workspace_override: str = "") -> dict[str, Any]:
|
|
87
|
+
install_status = collect_install_status(workspace_override)
|
|
88
|
+
doctor_status = collect_doctor_status(workspace_override)
|
|
89
|
+
package_audit = audit_package_runtime()
|
|
90
|
+
external_audit = audit_external_capabilities(workspace_override)
|
|
91
|
+
lifecycle_docs = audit_document_set(LIFECYCLE_REQUIRED_PATHS)
|
|
92
|
+
roadmap_assets = audit_document_set(ROADMAP_REQUIRED_PATHS)
|
|
93
|
+
deploy_signals = collect_deploy_signals()
|
|
94
|
+
|
|
95
|
+
install_ready = bool(install_status.get("summary", {}).get("project_ready"))
|
|
96
|
+
doctor_ready = bool(doctor_status.get("summary", {}).get("ready"))
|
|
97
|
+
package_ready = bool(package_audit.get("summary", {}).get("ready"))
|
|
98
|
+
external_ready = bool(external_audit.get("summary", {}).get("ready"))
|
|
99
|
+
lifecycle_ready = bool(lifecycle_docs.get("ready"))
|
|
100
|
+
roadmap_ready = bool(roadmap_assets.get("ready"))
|
|
101
|
+
|
|
102
|
+
ready = all([install_ready, doctor_ready, package_ready, external_ready, lifecycle_ready, roadmap_ready])
|
|
103
|
+
human_summary = "release / maintenance / ops 总检查通过。"
|
|
104
|
+
if not ready:
|
|
105
|
+
human_summary = "release / maintenance / ops 总检查仍有阻塞项。"
|
|
106
|
+
|
|
107
|
+
recommended_next_action = ""
|
|
108
|
+
if not install_ready:
|
|
109
|
+
recommended_next_action = str(install_status.get("summary", {}).get("recommended_project_command", "")).strip() or "cgc-install"
|
|
110
|
+
elif not doctor_ready:
|
|
111
|
+
recommended_next_action = "cgc-doctor"
|
|
112
|
+
elif not external_ready:
|
|
113
|
+
recommended_next_action = "cgc-external-audit"
|
|
114
|
+
elif not package_ready:
|
|
115
|
+
recommended_next_action = "cgc-package-audit"
|
|
116
|
+
elif not lifecycle_ready or not roadmap_ready:
|
|
117
|
+
recommended_next_action = "检查 codecgc/reference/release-maintenance-playbook.md 与 codecgc/roadmap/codecgc-release-maintenance/ 下的生命周期资产。"
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
"success": ready,
|
|
121
|
+
"mode": "release-readiness-audit",
|
|
122
|
+
"workspace": str(install_status.get("workspace", "")),
|
|
123
|
+
"summary": {
|
|
124
|
+
"ready": ready,
|
|
125
|
+
"scope": "release / maintenance / ops 就绪状态",
|
|
126
|
+
"human_summary": human_summary,
|
|
127
|
+
"recommended_next_action": recommended_next_action,
|
|
128
|
+
"install_ready": install_ready,
|
|
129
|
+
"doctor_ready": doctor_ready,
|
|
130
|
+
"package_ready": package_ready,
|
|
131
|
+
"external_ready": external_ready,
|
|
132
|
+
"lifecycle_ready": lifecycle_ready,
|
|
133
|
+
"roadmap_ready": roadmap_ready,
|
|
134
|
+
"deploy_signals_detected": deploy_signals["deploy_signals_detected"],
|
|
135
|
+
"deploy_readiness_stage": deploy_signals["deploy_readiness_stage"],
|
|
136
|
+
},
|
|
137
|
+
"install_status": install_status,
|
|
138
|
+
"doctor_status": doctor_status,
|
|
139
|
+
"package_audit": package_audit,
|
|
140
|
+
"external_audit": external_audit,
|
|
141
|
+
"lifecycle_docs": lifecycle_docs,
|
|
142
|
+
"roadmap_assets": roadmap_assets,
|
|
143
|
+
"deploy_signals": deploy_signals,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def build_summary(result: dict[str, Any]) -> str:
|
|
148
|
+
summary = result.get("summary", {}) if isinstance(result.get("summary"), dict) else {}
|
|
149
|
+
lifecycle_docs = result.get("lifecycle_docs", {}) if isinstance(result.get("lifecycle_docs"), dict) else {}
|
|
150
|
+
roadmap_assets = result.get("roadmap_assets", {}) if isinstance(result.get("roadmap_assets"), dict) else {}
|
|
151
|
+
deploy_signals = result.get("deploy_signals", {}) if isinstance(result.get("deploy_signals"), dict) else {}
|
|
152
|
+
lines = [
|
|
153
|
+
f"- 工作区: {result.get('workspace', '')}",
|
|
154
|
+
f"- 范围: {summary.get('scope', '')}",
|
|
155
|
+
f"- 就绪: {'是' if summary.get('ready') else '否'}",
|
|
156
|
+
f"- 摘要: {summary.get('human_summary', '')}",
|
|
157
|
+
f"- 项目级集成: {'就绪' if summary.get('install_ready') else '未就绪'}",
|
|
158
|
+
f"- 运行时自检: {'通过' if summary.get('doctor_ready') else '未通过'}",
|
|
159
|
+
f"- 发布包检查: {'通过' if summary.get('package_ready') else '未通过'}",
|
|
160
|
+
f"- 外部能力检查: {'通过' if summary.get('external_ready') else '未通过'}",
|
|
161
|
+
f"- 生命周期资产: {'齐全' if summary.get('lifecycle_ready') else '缺失'}",
|
|
162
|
+
f"- roadmap 资产: {'齐全' if summary.get('roadmap_ready') else '缺失'}",
|
|
163
|
+
f"- Deploy readiness stage: {summary.get('deploy_readiness_stage', '')}",
|
|
164
|
+
]
|
|
165
|
+
for signal_name, items in deploy_signals.get("deploy_signals_detected", {}).items():
|
|
166
|
+
if not isinstance(items, list):
|
|
167
|
+
continue
|
|
168
|
+
lines.append(f"- Deploy signal {signal_name}: {', '.join(str(item) for item in items) or '无'}")
|
|
169
|
+
for item in lifecycle_docs.get("missing", []):
|
|
170
|
+
lines.append(f"- 缺少生命周期资产: {item}")
|
|
171
|
+
for item in roadmap_assets.get("missing", []):
|
|
172
|
+
lines.append(f"- 缺少 roadmap 资产: {item}")
|
|
173
|
+
next_action = str(summary.get("recommended_next_action", "")).strip()
|
|
174
|
+
next_actions = [next_action] if next_action else []
|
|
175
|
+
return render_summary_block("CodeCGC Release Readiness", lines, next_actions)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def main() -> int:
|
|
179
|
+
parser = argparse.ArgumentParser(description="Run the combined CodeCGC release, maintenance, and ops readiness audit.")
|
|
180
|
+
parser.add_argument(
|
|
181
|
+
"--format",
|
|
182
|
+
choices=["json", "summary"],
|
|
183
|
+
default="summary",
|
|
184
|
+
help="Output format. Summary is the default product-facing mode; use json for debugging or automation.",
|
|
185
|
+
)
|
|
186
|
+
parser.add_argument(
|
|
187
|
+
"--workspace",
|
|
188
|
+
default="",
|
|
189
|
+
help="Optional target workspace root. Defaults to the current shell workspace.",
|
|
190
|
+
)
|
|
191
|
+
args = parser.parse_args()
|
|
192
|
+
|
|
193
|
+
result = audit_release_readiness(args.workspace)
|
|
194
|
+
if args.format == "summary":
|
|
195
|
+
print(build_summary(result))
|
|
196
|
+
else:
|
|
197
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
198
|
+
return 0 if result.get("success") else 1
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
if __name__ == "__main__":
|
|
202
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from refresh_codecgc_review_policy import collect_refresh_candidates
|
|
7
|
+
from refresh_codecgc_review_policy import normalize_artifact_class
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
11
|
+
parser = argparse.ArgumentParser(
|
|
12
|
+
description="检查历史 CodeCGC 审核产物是否缺少已持久化的策略字段。"
|
|
13
|
+
)
|
|
14
|
+
parser.add_argument(
|
|
15
|
+
"--artifact-class",
|
|
16
|
+
choices=["product", "fixture", "all"],
|
|
17
|
+
default="all",
|
|
18
|
+
help="要扫描的产物类别根目录。默认同时检查 product 和 fixture。",
|
|
19
|
+
)
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"--format",
|
|
22
|
+
choices=["json", "summary"],
|
|
23
|
+
default="json",
|
|
24
|
+
help="输出格式。summary 更适合维护与发布检查。",
|
|
25
|
+
)
|
|
26
|
+
return parser
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build_result(scan_target: str) -> dict[str, Any]:
|
|
30
|
+
normalized_target = normalize_artifact_class(scan_target) if scan_target != "all" else "all"
|
|
31
|
+
candidates = collect_refresh_candidates(normalized_target)
|
|
32
|
+
missing_items: list[dict[str, str]] = []
|
|
33
|
+
for item in candidates:
|
|
34
|
+
missing_items.append(
|
|
35
|
+
{
|
|
36
|
+
"artifact_class": str(item.get("artifact_class", "")),
|
|
37
|
+
"artifact_type": str(item.get("artifact_type", "")),
|
|
38
|
+
"artifact_path": str(item.get("artifact_path", "")),
|
|
39
|
+
"audit_path": str(item.get("audit_path", "")),
|
|
40
|
+
"decision": str(item.get("decision", "")),
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
return {
|
|
44
|
+
"success": len(missing_items) == 0,
|
|
45
|
+
"scan_target": scan_target,
|
|
46
|
+
"missing_count": len(missing_items),
|
|
47
|
+
"missing": missing_items,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def build_summary(result: dict[str, Any]) -> str:
|
|
52
|
+
lines = [
|
|
53
|
+
"CodeCGC 审核策略一致性检查",
|
|
54
|
+
f"- 范围: {result.get('scan_target', '')}",
|
|
55
|
+
f"- 缺失项数: {result.get('missing_count', 0)}",
|
|
56
|
+
f"- 就绪: {'是' if result.get('success') else '否'}",
|
|
57
|
+
]
|
|
58
|
+
for item in result.get("missing", []):
|
|
59
|
+
if not isinstance(item, dict):
|
|
60
|
+
continue
|
|
61
|
+
lines.append(
|
|
62
|
+
"- 缺失: "
|
|
63
|
+
+ f"{item.get('artifact_path', '')} "
|
|
64
|
+
+ f"[{item.get('artifact_class', '')}/{item.get('decision', '')}]"
|
|
65
|
+
)
|
|
66
|
+
return "\n".join(lines)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def main() -> int:
|
|
70
|
+
parser = build_parser()
|
|
71
|
+
args = parser.parse_args()
|
|
72
|
+
result = build_result(args.artifact_class)
|
|
73
|
+
|
|
74
|
+
if args.format == "summary":
|
|
75
|
+
print(build_summary(result))
|
|
76
|
+
else:
|
|
77
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
78
|
+
return 0 if result["success"] else 1
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from codecgc_artifact_roots import FIXTURE_ROOT
|
|
10
|
+
from codecgc_artifact_roots import PRODUCT_ROOT
|
|
11
|
+
from codecgc_console_io import render_summary_block
|
|
12
|
+
from codecgc_runtime_paths import PROJECT_ROOT
|
|
13
|
+
from route_codecgc_workflow import attach_route_summary
|
|
14
|
+
from route_codecgc_workflow import route_feature
|
|
15
|
+
from route_codecgc_workflow import route_issue
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
WORKSPACE = PROJECT_ROOT
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
FLOW_CONFIG: dict[str, dict[str, str]] = {
|
|
22
|
+
"feature": {
|
|
23
|
+
"plural": "features",
|
|
24
|
+
"summary_suffix": "-acceptance.md",
|
|
25
|
+
},
|
|
26
|
+
"issue": {
|
|
27
|
+
"plural": "issues",
|
|
28
|
+
"summary_suffix": "-fix-note.md",
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
STATE_LABELS = {
|
|
34
|
+
"needs-planning": "待规划",
|
|
35
|
+
"awaiting-build": "待功能执行",
|
|
36
|
+
"awaiting-fix": "待问题修复",
|
|
37
|
+
"awaiting-review": "待审核",
|
|
38
|
+
"closed": "已关闭",
|
|
39
|
+
"step-selected": "已选步骤",
|
|
40
|
+
"unknown": "未知",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def parse_iso_like_timestamp(value: str) -> datetime | None:
|
|
45
|
+
text = str(value or "").strip()
|
|
46
|
+
if not text:
|
|
47
|
+
return None
|
|
48
|
+
normalized = text.replace("Z", "+00:00")
|
|
49
|
+
try:
|
|
50
|
+
return datetime.fromisoformat(normalized)
|
|
51
|
+
except Exception:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def extract_created_from_slug(slug: str) -> str:
|
|
56
|
+
text = str(slug or "").strip()
|
|
57
|
+
if len(text) >= 10 and text[4] == "-" and text[7] == "-":
|
|
58
|
+
return text[:10]
|
|
59
|
+
return ""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def artifact_root(artifact_class: str) -> Path:
|
|
63
|
+
return FIXTURE_ROOT if artifact_class == "fixture" else PRODUCT_ROOT
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def collect_flow_directories(flow: str, include_fixtures: bool) -> list[tuple[str, Path]]:
|
|
67
|
+
config = FLOW_CONFIG[flow]
|
|
68
|
+
roots: list[tuple[str, Path]] = [("product", PRODUCT_ROOT / config["plural"])]
|
|
69
|
+
if include_fixtures:
|
|
70
|
+
roots.append(("fixture", FIXTURE_ROOT / config["plural"]))
|
|
71
|
+
|
|
72
|
+
discovered: list[tuple[str, Path]] = []
|
|
73
|
+
for artifact_class, root in roots:
|
|
74
|
+
if not root.exists():
|
|
75
|
+
continue
|
|
76
|
+
for item in sorted(root.iterdir()):
|
|
77
|
+
if item.is_dir():
|
|
78
|
+
discovered.append((artifact_class, item))
|
|
79
|
+
return discovered
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def locate_summary_file(flow: str, directory: Path) -> str:
|
|
83
|
+
suffix = FLOW_CONFIG[flow]["summary_suffix"]
|
|
84
|
+
matches = sorted(directory.glob(f"*{suffix}"))
|
|
85
|
+
if not matches:
|
|
86
|
+
return ""
|
|
87
|
+
return str(matches[0].resolve())
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def collect_history_record(flow: str, artifact_class: str, directory: Path) -> dict[str, Any]:
|
|
91
|
+
slug = directory.name
|
|
92
|
+
raw_route = route_feature(slug) if flow == "feature" else route_issue(slug)
|
|
93
|
+
route = attach_route_summary(raw_route)
|
|
94
|
+
summary = route.get("summary", {}) if isinstance(route.get("summary"), dict) else {}
|
|
95
|
+
workflow_state = str(summary.get("workflow_state", "")).strip() or "unknown"
|
|
96
|
+
current_step = route.get("current_step", {}) if isinstance(route.get("current_step"), dict) else {}
|
|
97
|
+
audit_path = str(route.get("audit_path", "")).strip()
|
|
98
|
+
audit_timestamp = ""
|
|
99
|
+
if audit_path:
|
|
100
|
+
audit_time = parse_iso_like_timestamp(Path(audit_path).stat().st_mtime_ns and datetime.fromtimestamp(Path(audit_path).stat().st_mtime).isoformat())
|
|
101
|
+
if audit_time:
|
|
102
|
+
audit_timestamp = audit_time.isoformat()
|
|
103
|
+
|
|
104
|
+
record: dict[str, Any] = {
|
|
105
|
+
"flow": flow,
|
|
106
|
+
"artifact_class": artifact_class,
|
|
107
|
+
"slug": slug,
|
|
108
|
+
"created": extract_created_from_slug(slug),
|
|
109
|
+
"directory": str(directory.resolve()),
|
|
110
|
+
"workflow_state": workflow_state,
|
|
111
|
+
"state_label": STATE_LABELS.get(workflow_state, workflow_state or "未知"),
|
|
112
|
+
"recommended_command": str(route.get("recommended_command", "")).strip(),
|
|
113
|
+
"human_summary": str(summary.get("human_summary", "")).strip() or str(route.get("reason", "")).strip(),
|
|
114
|
+
"next": str(route.get("next", "")).strip(),
|
|
115
|
+
"current_step_number": int(summary.get("current_step_number", 0) or 0),
|
|
116
|
+
"current_task_id": str(summary.get("current_task_id", "")).strip(),
|
|
117
|
+
"review_decision": str(summary.get("review_decision", "")).strip(),
|
|
118
|
+
"is_closed": bool(summary.get("is_closed")),
|
|
119
|
+
"audit_path": audit_path,
|
|
120
|
+
"audit_timestamp": audit_timestamp,
|
|
121
|
+
"summary_file": locate_summary_file(flow, directory),
|
|
122
|
+
"root": str(artifact_root(artifact_class).resolve()),
|
|
123
|
+
}
|
|
124
|
+
if current_step:
|
|
125
|
+
record["current_step"] = {
|
|
126
|
+
"step_number": int(current_step.get("step_number", 0) or 0),
|
|
127
|
+
"task_id": str(current_step.get("task_id", "")).strip(),
|
|
128
|
+
"kind": str(current_step.get("kind", "")).strip(),
|
|
129
|
+
"target_paths": current_step.get("target_paths", []),
|
|
130
|
+
"task_summary": str(current_step.get("task_summary", "")).strip(),
|
|
131
|
+
}
|
|
132
|
+
return record
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def build_sort_key(record: dict[str, Any]) -> tuple[datetime, str]:
|
|
136
|
+
audit_dt = parse_iso_like_timestamp(str(record.get("audit_timestamp", "")).strip())
|
|
137
|
+
if audit_dt is not None:
|
|
138
|
+
return audit_dt, str(record.get("slug", ""))
|
|
139
|
+
|
|
140
|
+
created_text = str(record.get("created", "")).strip()
|
|
141
|
+
created_dt = parse_iso_like_timestamp(f"{created_text}T00:00:00") if created_text else None
|
|
142
|
+
if created_dt is not None:
|
|
143
|
+
return created_dt, str(record.get("slug", ""))
|
|
144
|
+
|
|
145
|
+
directory = Path(str(record.get("directory", "")).strip())
|
|
146
|
+
try:
|
|
147
|
+
fallback = datetime.fromtimestamp(directory.stat().st_mtime)
|
|
148
|
+
except Exception:
|
|
149
|
+
fallback = datetime.fromtimestamp(0)
|
|
150
|
+
return fallback, str(record.get("slug", ""))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def normalize_status_filter(value: str) -> str:
|
|
154
|
+
text = str(value or "all").strip().lower()
|
|
155
|
+
aliases = {
|
|
156
|
+
"all": "all",
|
|
157
|
+
"open": "open",
|
|
158
|
+
"closed": "closed",
|
|
159
|
+
"planning": "needs-planning",
|
|
160
|
+
"needs-planning": "needs-planning",
|
|
161
|
+
"build": "awaiting-build",
|
|
162
|
+
"awaiting-build": "awaiting-build",
|
|
163
|
+
"fix": "awaiting-fix",
|
|
164
|
+
"awaiting-fix": "awaiting-fix",
|
|
165
|
+
"review": "awaiting-review",
|
|
166
|
+
"awaiting-review": "awaiting-review",
|
|
167
|
+
"selected": "step-selected",
|
|
168
|
+
"step-selected": "step-selected",
|
|
169
|
+
"unknown": "unknown",
|
|
170
|
+
}
|
|
171
|
+
return aliases.get(text, text)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def status_matches(record: dict[str, Any], status_filter: str) -> bool:
|
|
175
|
+
normalized = normalize_status_filter(status_filter)
|
|
176
|
+
state = str(record.get("workflow_state", "")).strip()
|
|
177
|
+
if normalized == "all":
|
|
178
|
+
return True
|
|
179
|
+
if normalized == "open":
|
|
180
|
+
return not bool(record.get("is_closed"))
|
|
181
|
+
if normalized == "closed":
|
|
182
|
+
return bool(record.get("is_closed"))
|
|
183
|
+
return state == normalized
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def collect_history(flow: str, status_filter: str, last: int, include_fixtures: bool) -> dict[str, Any]:
|
|
187
|
+
flows = ["feature", "issue"] if flow == "all" else [flow]
|
|
188
|
+
records: list[dict[str, Any]] = []
|
|
189
|
+
scanned = 0
|
|
190
|
+
for current_flow in flows:
|
|
191
|
+
for artifact_class, directory in collect_flow_directories(current_flow, include_fixtures):
|
|
192
|
+
scanned += 1
|
|
193
|
+
record = collect_history_record(current_flow, artifact_class, directory)
|
|
194
|
+
if status_matches(record, status_filter):
|
|
195
|
+
records.append(record)
|
|
196
|
+
|
|
197
|
+
records.sort(key=build_sort_key, reverse=True)
|
|
198
|
+
limited_records = records[:last] if last > 0 else records
|
|
199
|
+
|
|
200
|
+
open_count = sum(1 for item in limited_records if not item.get("is_closed"))
|
|
201
|
+
closed_count = sum(1 for item in limited_records if item.get("is_closed"))
|
|
202
|
+
state_breakdown: dict[str, int] = {}
|
|
203
|
+
for item in limited_records:
|
|
204
|
+
state = str(item.get("workflow_state", "unknown")).strip() or "unknown"
|
|
205
|
+
state_breakdown[state] = state_breakdown.get(state, 0) + 1
|
|
206
|
+
|
|
207
|
+
flow_label = "全部工作流" if flow == "all" else flow
|
|
208
|
+
summary_filter = normalize_status_filter(status_filter)
|
|
209
|
+
human_summary = f"已汇总最近 {len(limited_records)} 条 {flow_label} 历史。"
|
|
210
|
+
if summary_filter != "all":
|
|
211
|
+
human_summary = f"已汇总最近 {len(limited_records)} 条 {flow_label} 历史,过滤条件为 {summary_filter}。"
|
|
212
|
+
|
|
213
|
+
recommended_command = ""
|
|
214
|
+
next_action = "继续使用 cgc-route / cgc-entry / cgc-plan 进入你要跟进的那条工作流。"
|
|
215
|
+
if limited_records:
|
|
216
|
+
first = limited_records[0]
|
|
217
|
+
recommended_command = str(first.get("recommended_command", "")).strip()
|
|
218
|
+
slug = str(first.get("slug", "")).strip()
|
|
219
|
+
first_flow = str(first.get("flow", "")).strip()
|
|
220
|
+
if recommended_command and slug and first_flow:
|
|
221
|
+
if recommended_command == "cgc-route":
|
|
222
|
+
next_action = f"如需继续最近一条工作流,先运行 cgc-route --flow {first_flow} --slug {slug}。"
|
|
223
|
+
elif recommended_command in {"cgc-build", "cgc-fix"}:
|
|
224
|
+
next_action = f"如需继续最近一条工作流,可直接运行 {recommended_command} --slug {slug}。"
|
|
225
|
+
else:
|
|
226
|
+
next_action = f"如需继续最近一条工作流,可先运行 {recommended_command} 并带上 slug {slug}。"
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
"success": True,
|
|
230
|
+
"mode": "workflow-history",
|
|
231
|
+
"workspace": str(WORKSPACE),
|
|
232
|
+
"filters": {
|
|
233
|
+
"flow": flow,
|
|
234
|
+
"status": summary_filter,
|
|
235
|
+
"last": last,
|
|
236
|
+
"include_fixtures": include_fixtures,
|
|
237
|
+
},
|
|
238
|
+
"summary": {
|
|
239
|
+
"human_summary": human_summary,
|
|
240
|
+
"recommended_command": recommended_command,
|
|
241
|
+
"next_action": next_action,
|
|
242
|
+
"matched_count": len(records),
|
|
243
|
+
"returned_count": len(limited_records),
|
|
244
|
+
"open_count": open_count,
|
|
245
|
+
"closed_count": closed_count,
|
|
246
|
+
"state_breakdown": state_breakdown,
|
|
247
|
+
},
|
|
248
|
+
"scanned_workflows": scanned,
|
|
249
|
+
"records": limited_records,
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def build_summary(result: dict[str, Any]) -> str:
|
|
254
|
+
summary = result.get("summary", {}) if isinstance(result.get("summary"), dict) else {}
|
|
255
|
+
filters = result.get("filters", {}) if isinstance(result.get("filters"), dict) else {}
|
|
256
|
+
lines = [
|
|
257
|
+
f"- 工作区: {result.get('workspace', '')}",
|
|
258
|
+
f"- Flow 过滤: {filters.get('flow', '')}",
|
|
259
|
+
f"- 状态过滤: {filters.get('status', '')}",
|
|
260
|
+
f"- 返回条数: {summary.get('returned_count', 0)} / 匹配条数: {summary.get('matched_count', 0)}",
|
|
261
|
+
f"- 已关闭: {summary.get('closed_count', 0)}",
|
|
262
|
+
f"- 未关闭: {summary.get('open_count', 0)}",
|
|
263
|
+
f"- 摘要: {summary.get('human_summary', '')}",
|
|
264
|
+
]
|
|
265
|
+
breakdown = summary.get("state_breakdown", {}) if isinstance(summary.get("state_breakdown"), dict) else {}
|
|
266
|
+
for state, count in breakdown.items():
|
|
267
|
+
lines.append(f"- 状态分布: {state} = {count}")
|
|
268
|
+
|
|
269
|
+
for item in result.get("records", []):
|
|
270
|
+
if not isinstance(item, dict):
|
|
271
|
+
continue
|
|
272
|
+
slug = str(item.get("slug", "")).strip()
|
|
273
|
+
flow = str(item.get("flow", "")).strip()
|
|
274
|
+
artifact_class = str(item.get("artifact_class", "")).strip()
|
|
275
|
+
state_label = str(item.get("state_label", "")).strip()
|
|
276
|
+
command = str(item.get("recommended_command", "")).strip() or "无"
|
|
277
|
+
created = str(item.get("created", "")).strip() or "unknown"
|
|
278
|
+
lines.append(
|
|
279
|
+
f"- 历史: [{flow}/{artifact_class}] {slug} | {state_label} | created={created} | next={command}"
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
next_action = str(summary.get("next_action", "")).strip()
|
|
283
|
+
next_actions = [next_action] if next_action else []
|
|
284
|
+
return render_summary_block("CodeCGC Workflow History", lines, next_actions)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def main() -> int:
|
|
288
|
+
parser = argparse.ArgumentParser(
|
|
289
|
+
description="Read-only CodeCGC workflow history query across feature and issue artifacts."
|
|
290
|
+
)
|
|
291
|
+
parser.add_argument("--flow", choices=["all", "feature", "issue"], default="all")
|
|
292
|
+
parser.add_argument("--status", default="all")
|
|
293
|
+
parser.add_argument("--last", type=int, default=10)
|
|
294
|
+
parser.add_argument("--include-fixtures", action="store_true")
|
|
295
|
+
parser.add_argument(
|
|
296
|
+
"--format",
|
|
297
|
+
choices=["json", "summary"],
|
|
298
|
+
default="summary",
|
|
299
|
+
help="Output format. Summary is the default product-facing mode; use json for debugging or automation.",
|
|
300
|
+
)
|
|
301
|
+
args = parser.parse_args()
|
|
302
|
+
|
|
303
|
+
result = collect_history(
|
|
304
|
+
flow=args.flow,
|
|
305
|
+
status_filter=args.status,
|
|
306
|
+
last=max(args.last, 0),
|
|
307
|
+
include_fixtures=args.include_fixtures,
|
|
308
|
+
)
|
|
309
|
+
if args.format == "summary":
|
|
310
|
+
print(build_summary(result))
|
|
311
|
+
else:
|
|
312
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
313
|
+
return 0
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
if __name__ == "__main__":
|
|
317
|
+
raise SystemExit(main())
|