@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,233 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from codecgc_command_surface import matches_command
|
|
6
|
+
from codecgc_command_surface import to_public_command
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def extract_execution_result(execution: dict[str, Any]) -> dict[str, Any]:
|
|
10
|
+
result = execution.get("result", {}) if isinstance(execution, dict) else {}
|
|
11
|
+
if not isinstance(result, dict):
|
|
12
|
+
return {}
|
|
13
|
+
nested = result.get("result")
|
|
14
|
+
if isinstance(nested, dict):
|
|
15
|
+
return nested
|
|
16
|
+
return result
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def execution_is_ready_for_review(execution: dict[str, Any]) -> bool:
|
|
20
|
+
if not bool(execution.get("success")):
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
mode = str(execution.get("mode", "")).strip().lower()
|
|
24
|
+
result = extract_execution_result(execution)
|
|
25
|
+
policy_checks = result.get("policy_checks", [])
|
|
26
|
+
risks = result.get("risks", [])
|
|
27
|
+
if not isinstance(policy_checks, list):
|
|
28
|
+
policy_checks = []
|
|
29
|
+
if not isinstance(risks, list):
|
|
30
|
+
risks = []
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
mode != "dry-run"
|
|
34
|
+
and "dry_run_only" not in {str(item) for item in policy_checks}
|
|
35
|
+
and "execution_not_performed" not in {str(item) for item in risks}
|
|
36
|
+
and bool(result.get("success"))
|
|
37
|
+
and str(result.get("outcome", "")).strip().lower() == "done"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build_not_ready_result(flow: str, slug: str, route: dict[str, Any], expected_command: str) -> dict[str, Any]:
|
|
42
|
+
public_expected_command = to_public_command(expected_command)
|
|
43
|
+
flow_label = "测试执行" if expected_command == "cgc-test" else "功能开发" if flow == "feature" else "问题修复" if flow == "issue" else "当前"
|
|
44
|
+
result = {
|
|
45
|
+
"success": False,
|
|
46
|
+
"flow": flow,
|
|
47
|
+
"slug": slug,
|
|
48
|
+
"state": "not-ready",
|
|
49
|
+
"failure_type": "workflow-state",
|
|
50
|
+
"route": route,
|
|
51
|
+
"error": f"{flow_label}工作流当前还未满足 {public_expected_command} 的执行条件。",
|
|
52
|
+
"recommended_command": to_public_command(route.get("recommended_command", "cgc-plan")),
|
|
53
|
+
"next": route.get("next", "请先修复当前工作流产物与状态,再继续执行。"),
|
|
54
|
+
}
|
|
55
|
+
split_suggestion = route.get("split_suggestion", {}) if isinstance(route.get("split_suggestion"), dict) else {}
|
|
56
|
+
replan_payload = route.get("replan_payload", {}) if isinstance(route.get("replan_payload"), dict) else {}
|
|
57
|
+
if split_suggestion:
|
|
58
|
+
result["split_suggestion"] = split_suggestion
|
|
59
|
+
if replan_payload:
|
|
60
|
+
result["replan_payload"] = replan_payload
|
|
61
|
+
return result
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def classify_execution_failure(execution: dict[str, Any]) -> tuple[str, str, str, str]:
|
|
65
|
+
result = extract_execution_result(execution)
|
|
66
|
+
outcome = str(result.get("outcome", "")).strip().lower()
|
|
67
|
+
error_text = str(result.get("error", "") or execution.get("error", "")).strip()
|
|
68
|
+
summary_text = str(result.get("summary", "")).strip()
|
|
69
|
+
combined = f"{outcome}\n{error_text}\n{summary_text}".lower()
|
|
70
|
+
target_missing_markers = [
|
|
71
|
+
"target file path does not exist",
|
|
72
|
+
"target directory path does not exist",
|
|
73
|
+
"does not exist in the current workspace",
|
|
74
|
+
"current workspace",
|
|
75
|
+
"当前工作区中不存在目标文件",
|
|
76
|
+
"目标文件路径在当前工作区下不存在",
|
|
77
|
+
"在当前工作区里不存在",
|
|
78
|
+
"工作区里没有",
|
|
79
|
+
"目标文件路径在当前工作区里对不上",
|
|
80
|
+
"目标文件缺失",
|
|
81
|
+
"没有可实施对象",
|
|
82
|
+
"目标文件本身缺失",
|
|
83
|
+
"提供正确文件路径",
|
|
84
|
+
"恢复到工作区",
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
if outcome == "split-required" or "split the task first" in combined:
|
|
88
|
+
return (
|
|
89
|
+
"returned-to-planning",
|
|
90
|
+
"scope-error",
|
|
91
|
+
"cgc-plan",
|
|
92
|
+
"请先按执行器归属或路径范围拆分当前步骤,再重新生成更窄的可执行契约。",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if outcome == "design-gap" or "not covered by model-routing.yaml" in combined:
|
|
96
|
+
return (
|
|
97
|
+
"returned-to-planning",
|
|
98
|
+
"design-gap",
|
|
99
|
+
"cgc-plan",
|
|
100
|
+
"请先修正路由规则、目标路径或步骤契约,再重新尝试执行。",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if any(marker in combined for marker in target_missing_markers):
|
|
104
|
+
return (
|
|
105
|
+
"returned-to-planning",
|
|
106
|
+
"design-gap",
|
|
107
|
+
"cgc-plan",
|
|
108
|
+
"当前步骤引用的目标路径在工作区中不存在,请先回到 cgc-plan 修正目标路径或步骤契约。",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if outcome == "blocked" or "timeout" in combined or "does not exist" in combined:
|
|
112
|
+
return (
|
|
113
|
+
"blocked",
|
|
114
|
+
"environment-or-tooling",
|
|
115
|
+
"",
|
|
116
|
+
"请先修复缺失产物、执行器环境或超时条件,再重试当前步骤。",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
"blocked",
|
|
121
|
+
"executor-failure",
|
|
122
|
+
"",
|
|
123
|
+
"请先检查审计产物和执行器输出,修复执行器侧失败后再重试。",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def build_split_scope_replan_payload(flow: str, slug: str, execution: dict[str, Any]) -> dict[str, Any]:
|
|
128
|
+
result = extract_execution_result(execution)
|
|
129
|
+
split_suggestion = result.get("split_suggestion", {}) if isinstance(result.get("split_suggestion"), dict) else {}
|
|
130
|
+
if not split_suggestion:
|
|
131
|
+
return {}
|
|
132
|
+
|
|
133
|
+
grouped_paths = split_suggestion.get("grouped_paths", {}) if isinstance(split_suggestion.get("grouped_paths"), dict) else {}
|
|
134
|
+
path_classification = split_suggestion.get("path_classification", {}) if isinstance(split_suggestion.get("path_classification"), dict) else {}
|
|
135
|
+
suggested_steps = split_suggestion.get("suggested_split_steps", []) if isinstance(split_suggestion.get("suggested_split_steps"), list) else []
|
|
136
|
+
|
|
137
|
+
payload: dict[str, Any] = {
|
|
138
|
+
"flow": flow,
|
|
139
|
+
"slug": slug,
|
|
140
|
+
"kind": "auto",
|
|
141
|
+
"split_scope": True,
|
|
142
|
+
"path_classification": path_classification,
|
|
143
|
+
"grouped_paths": grouped_paths,
|
|
144
|
+
"suggested_split_steps": suggested_steps,
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
combined_paths: list[str] = []
|
|
148
|
+
for key in ("frontend", "backend", "shared"):
|
|
149
|
+
value = grouped_paths.get(key, [])
|
|
150
|
+
if isinstance(value, list):
|
|
151
|
+
combined_paths.extend(str(item).strip() for item in value if str(item).strip())
|
|
152
|
+
if combined_paths:
|
|
153
|
+
payload["target_paths"] = combined_paths
|
|
154
|
+
|
|
155
|
+
in_scope: list[str] = []
|
|
156
|
+
for item in suggested_steps:
|
|
157
|
+
if not isinstance(item, dict):
|
|
158
|
+
continue
|
|
159
|
+
kind = str(item.get("kind", "")).strip()
|
|
160
|
+
paths = [str(path).strip() for path in item.get("target_paths", []) if str(path).strip()]
|
|
161
|
+
if not paths:
|
|
162
|
+
continue
|
|
163
|
+
if kind == "planning":
|
|
164
|
+
in_scope.append(f"先拆分 shared 路径:{', '.join(paths)}。")
|
|
165
|
+
else:
|
|
166
|
+
in_scope.append(f"将 {kind} 范围拆成独立步骤:{', '.join(paths)}。")
|
|
167
|
+
if in_scope:
|
|
168
|
+
payload["in_scope"] = in_scope
|
|
169
|
+
|
|
170
|
+
return payload
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def build_execution_result(
|
|
174
|
+
*,
|
|
175
|
+
flow: str,
|
|
176
|
+
slug: str,
|
|
177
|
+
route: dict[str, Any],
|
|
178
|
+
execution: dict[str, Any],
|
|
179
|
+
) -> dict[str, Any]:
|
|
180
|
+
success = bool(execution.get("success"))
|
|
181
|
+
audit = execution.get("audit", {}) if isinstance(execution, dict) else {}
|
|
182
|
+
audit_path = audit.get("path", "") if isinstance(audit, dict) else ""
|
|
183
|
+
result = extract_execution_result(execution)
|
|
184
|
+
|
|
185
|
+
if execution_is_ready_for_review(execution):
|
|
186
|
+
return {
|
|
187
|
+
"success": True,
|
|
188
|
+
"flow": flow,
|
|
189
|
+
"slug": slug,
|
|
190
|
+
"state": "ready-for-review",
|
|
191
|
+
"failure_type": "",
|
|
192
|
+
"route": route,
|
|
193
|
+
"execution": execution,
|
|
194
|
+
"audit_path": audit_path,
|
|
195
|
+
"recommended_command": to_public_command("cgc-review"),
|
|
196
|
+
"next": "请检查执行审计结果,并写回通过或修复决策。",
|
|
197
|
+
"summary": result.get("summary", ""),
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if success:
|
|
201
|
+
retry_command = "cgc-test" if matches_command(str(route.get("recommended_command", "")).strip(), "cgc-test") else f"cgc-{'build' if flow == 'feature' else 'fix'}"
|
|
202
|
+
return {
|
|
203
|
+
"success": False,
|
|
204
|
+
"flow": flow,
|
|
205
|
+
"slug": slug,
|
|
206
|
+
"state": "executed-dry-run",
|
|
207
|
+
"failure_type": "workflow-state",
|
|
208
|
+
"route": route,
|
|
209
|
+
"execution": execution,
|
|
210
|
+
"audit_path": audit_path,
|
|
211
|
+
"recommended_command": to_public_command(retry_command),
|
|
212
|
+
"next": "请去掉 --dry-run 后重新执行同一步骤,再发起审核。",
|
|
213
|
+
"summary": result.get("summary", ""),
|
|
214
|
+
"error": "",
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
state, failure_type, recommended_command, next_step = classify_execution_failure(execution)
|
|
218
|
+
return {
|
|
219
|
+
"success": False,
|
|
220
|
+
"flow": flow,
|
|
221
|
+
"slug": slug,
|
|
222
|
+
"state": state,
|
|
223
|
+
"failure_type": failure_type,
|
|
224
|
+
"route": route,
|
|
225
|
+
"execution": execution,
|
|
226
|
+
"audit_path": audit_path,
|
|
227
|
+
"recommended_command": to_public_command(recommended_command),
|
|
228
|
+
"next": next_step,
|
|
229
|
+
"summary": result.get("summary", ""),
|
|
230
|
+
"error": result.get("error", "") or execution.get("error", ""),
|
|
231
|
+
"split_suggestion": result.get("split_suggestion", {}),
|
|
232
|
+
"replan_payload": build_split_scope_replan_payload(flow, slug, execution) if failure_type == "scope-error" else {},
|
|
233
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from collections import Counter
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
STOPWORDS = {
|
|
8
|
+
"a",
|
|
9
|
+
"an",
|
|
10
|
+
"and",
|
|
11
|
+
"as",
|
|
12
|
+
"at",
|
|
13
|
+
"be",
|
|
14
|
+
"for",
|
|
15
|
+
"from",
|
|
16
|
+
"in",
|
|
17
|
+
"is",
|
|
18
|
+
"of",
|
|
19
|
+
"on",
|
|
20
|
+
"or",
|
|
21
|
+
"the",
|
|
22
|
+
"to",
|
|
23
|
+
"use",
|
|
24
|
+
"using",
|
|
25
|
+
"with",
|
|
26
|
+
"将其",
|
|
27
|
+
"这条",
|
|
28
|
+
"一个",
|
|
29
|
+
"一种",
|
|
30
|
+
"作为",
|
|
31
|
+
"写入",
|
|
32
|
+
"记录",
|
|
33
|
+
"更新",
|
|
34
|
+
"默认",
|
|
35
|
+
"需要",
|
|
36
|
+
"当前",
|
|
37
|
+
"长期",
|
|
38
|
+
"治理",
|
|
39
|
+
"资产",
|
|
40
|
+
"说明",
|
|
41
|
+
"补充",
|
|
42
|
+
"继续",
|
|
43
|
+
"用于",
|
|
44
|
+
"后续",
|
|
45
|
+
"帮助",
|
|
46
|
+
"统一",
|
|
47
|
+
"暴露",
|
|
48
|
+
"输出",
|
|
49
|
+
"命令面",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def normalize_text(value: str) -> str:
|
|
54
|
+
text = str(value or "").strip().lower()
|
|
55
|
+
if not text:
|
|
56
|
+
return ""
|
|
57
|
+
text = text.replace("`", "")
|
|
58
|
+
replacements = {
|
|
59
|
+
"只暴露": "唯一对外",
|
|
60
|
+
"只使用": "统一使用",
|
|
61
|
+
"命令面": "命令",
|
|
62
|
+
"输出链路": "输出",
|
|
63
|
+
"使用 utf 8": "utf8",
|
|
64
|
+
"utf-8": "utf8",
|
|
65
|
+
"utf 8": "utf8",
|
|
66
|
+
}
|
|
67
|
+
for old, new in replacements.items():
|
|
68
|
+
text = text.replace(old, new)
|
|
69
|
+
text = re.sub(r"[^\w\u4e00-\u9fff]+", " ", text, flags=re.UNICODE)
|
|
70
|
+
return re.sub(r"\s+", " ", text).strip()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def tokenize(value: str) -> list[str]:
|
|
74
|
+
normalized = normalize_text(value)
|
|
75
|
+
if not normalized:
|
|
76
|
+
return []
|
|
77
|
+
|
|
78
|
+
tokens: list[str] = []
|
|
79
|
+
for part in normalized.split():
|
|
80
|
+
if not part or part in STOPWORDS:
|
|
81
|
+
continue
|
|
82
|
+
if re.fullmatch(r"[\u4e00-\u9fff]+", part):
|
|
83
|
+
if len(part) <= 2:
|
|
84
|
+
tokens.append(part)
|
|
85
|
+
else:
|
|
86
|
+
tokens.extend([part[index : index + 2] for index in range(len(part) - 1)])
|
|
87
|
+
continue
|
|
88
|
+
if len(part) <= 1:
|
|
89
|
+
continue
|
|
90
|
+
tokens.append(part)
|
|
91
|
+
return tokens
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def extract_entry_summaries(existing: str, field_labels: list[str]) -> list[str]:
|
|
95
|
+
lines = str(existing or "").splitlines()
|
|
96
|
+
summaries: list[str] = []
|
|
97
|
+
for line in lines:
|
|
98
|
+
stripped = line.strip()
|
|
99
|
+
if stripped.startswith("### "):
|
|
100
|
+
heading = stripped[4:].strip()
|
|
101
|
+
if heading:
|
|
102
|
+
summaries.append(heading)
|
|
103
|
+
continue
|
|
104
|
+
for label in field_labels:
|
|
105
|
+
marker = f"- {label}:"
|
|
106
|
+
if stripped.startswith(marker):
|
|
107
|
+
value = stripped[len(marker) :].strip()
|
|
108
|
+
if value:
|
|
109
|
+
summaries.append(value)
|
|
110
|
+
seen: set[str] = set()
|
|
111
|
+
unique: list[str] = []
|
|
112
|
+
for item in summaries:
|
|
113
|
+
key = normalize_text(item)
|
|
114
|
+
if not key or key in seen:
|
|
115
|
+
continue
|
|
116
|
+
seen.add(key)
|
|
117
|
+
unique.append(item.strip())
|
|
118
|
+
return unique
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def is_semantically_duplicate(candidate: str, existing_items: list[str], *, threshold: float = 0.72) -> bool:
|
|
122
|
+
candidate_tokens = tokenize(candidate)
|
|
123
|
+
if not candidate_tokens:
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
candidate_counter = Counter(candidate_tokens)
|
|
127
|
+
candidate_set = set(candidate_counter)
|
|
128
|
+
candidate_normalized = normalize_text(candidate)
|
|
129
|
+
|
|
130
|
+
for item in existing_items:
|
|
131
|
+
item_normalized = normalize_text(item)
|
|
132
|
+
if not item_normalized:
|
|
133
|
+
continue
|
|
134
|
+
if item_normalized == candidate_normalized:
|
|
135
|
+
return True
|
|
136
|
+
|
|
137
|
+
item_tokens = tokenize(item)
|
|
138
|
+
if not item_tokens:
|
|
139
|
+
continue
|
|
140
|
+
item_counter = Counter(item_tokens)
|
|
141
|
+
item_set = set(item_counter)
|
|
142
|
+
overlap = candidate_set & item_set
|
|
143
|
+
if not overlap:
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
overlap_count = sum(min(candidate_counter[token], item_counter[token]) for token in overlap)
|
|
147
|
+
similarity = overlap_count / max(len(candidate_tokens), len(item_tokens))
|
|
148
|
+
coverage = len(overlap) / max(1, min(len(candidate_set), len(item_set)))
|
|
149
|
+
candidate_normalized_words = set(normalize_text(candidate).split())
|
|
150
|
+
item_normalized_words = set(item_normalized.split())
|
|
151
|
+
word_overlap = candidate_normalized_words & item_normalized_words
|
|
152
|
+
word_coverage = len(word_overlap) / max(1, min(len(candidate_normalized_words), len(item_normalized_words)))
|
|
153
|
+
if similarity >= threshold or (similarity >= 0.58 and coverage >= 0.8) or (similarity >= 0.42 and word_coverage >= 0.6):
|
|
154
|
+
return True
|
|
155
|
+
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def has_existing_governance_entry(existing: str, summary: str, *, field_labels: list[str], threshold: float = 0.72) -> bool:
|
|
160
|
+
summaries = extract_entry_summaries(existing, field_labels)
|
|
161
|
+
return is_semantically_duplicate(summary, summaries, threshold=threshold)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from codecgc_command_surface import to_public_command
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def evaluate_plan_decision(
|
|
9
|
+
*,
|
|
10
|
+
flow: str,
|
|
11
|
+
grouped_paths: dict[str, list[str]],
|
|
12
|
+
target_paths: list[str],
|
|
13
|
+
executable_steps: int,
|
|
14
|
+
planning_only_steps: int,
|
|
15
|
+
goal: str,
|
|
16
|
+
user_story: str,
|
|
17
|
+
symptom: str,
|
|
18
|
+
expected: str,
|
|
19
|
+
actual: str,
|
|
20
|
+
in_scope: list[str],
|
|
21
|
+
acceptance: list[str],
|
|
22
|
+
) -> dict[str, Any]:
|
|
23
|
+
clarification_reasons: list[str] = []
|
|
24
|
+
roadmap_reasons: list[str] = []
|
|
25
|
+
missing_fields: list[str] = []
|
|
26
|
+
|
|
27
|
+
if not target_paths:
|
|
28
|
+
clarification_reasons.append("尚未提供目标路径。")
|
|
29
|
+
missing_fields.append("target_paths")
|
|
30
|
+
|
|
31
|
+
if grouped_paths.get("unknown"):
|
|
32
|
+
clarification_reasons.append("部分目标路径尚未被 model-routing.yaml 覆盖。")
|
|
33
|
+
missing_fields.append("routing_coverage")
|
|
34
|
+
|
|
35
|
+
if planning_only_steps > 0:
|
|
36
|
+
clarification_reasons.append("在执行开始前,仍有仅规划 step 需要先处理。")
|
|
37
|
+
missing_fields.append("planning_blockers")
|
|
38
|
+
|
|
39
|
+
if executable_steps < 1:
|
|
40
|
+
clarification_reasons.append("当前还没有准备好可执行的单归属 step。")
|
|
41
|
+
missing_fields.append("executable_step")
|
|
42
|
+
|
|
43
|
+
if flow == "feature":
|
|
44
|
+
if not goal.strip():
|
|
45
|
+
clarification_reasons.append("功能目标仍未补齐。")
|
|
46
|
+
missing_fields.append("goal")
|
|
47
|
+
if not user_story.strip():
|
|
48
|
+
clarification_reasons.append("用户故事仍未补齐。")
|
|
49
|
+
missing_fields.append("user_story")
|
|
50
|
+
else:
|
|
51
|
+
if not symptom.strip():
|
|
52
|
+
clarification_reasons.append("问题现象仍未补齐。")
|
|
53
|
+
missing_fields.append("symptom")
|
|
54
|
+
if not expected.strip():
|
|
55
|
+
clarification_reasons.append("预期行为仍未补齐。")
|
|
56
|
+
missing_fields.append("expected")
|
|
57
|
+
if not actual.strip():
|
|
58
|
+
clarification_reasons.append("实际行为仍未补齐。")
|
|
59
|
+
missing_fields.append("actual")
|
|
60
|
+
|
|
61
|
+
if not in_scope:
|
|
62
|
+
clarification_reasons.append("范围内行为仍不明确。")
|
|
63
|
+
missing_fields.append("in_scope")
|
|
64
|
+
|
|
65
|
+
if not acceptance:
|
|
66
|
+
clarification_reasons.append("验收条件仍未补齐。")
|
|
67
|
+
missing_fields.append("acceptance")
|
|
68
|
+
|
|
69
|
+
if executable_steps >= 3:
|
|
70
|
+
roadmap_reasons.append("当前计划已经跨越 3 个或以上可执行 step。")
|
|
71
|
+
|
|
72
|
+
if len(target_paths) >= 6:
|
|
73
|
+
roadmap_reasons.append("当前计划涉及的目标路径过多,已经超出单个 feature 级流转的合理范围。")
|
|
74
|
+
|
|
75
|
+
if grouped_paths.get("frontend") and grouped_paths.get("backend") and planning_only_steps > 0:
|
|
76
|
+
roadmap_reasons.append("跨前后端边界的工作仍需要更高层级的拆分。")
|
|
77
|
+
|
|
78
|
+
if roadmap_reasons:
|
|
79
|
+
return {
|
|
80
|
+
"planning_status": "needs-roadmap",
|
|
81
|
+
"recommended_command": to_public_command("cgc-plan"),
|
|
82
|
+
"next": "需要先在 roadmap 层完成拆分,再继续进入可执行的 feature 或 issue step。",
|
|
83
|
+
"reasons": roadmap_reasons,
|
|
84
|
+
"missing_fields": [],
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if clarification_reasons:
|
|
88
|
+
return {
|
|
89
|
+
"planning_status": "needs-clarification",
|
|
90
|
+
"recommended_command": to_public_command("cgc-plan"),
|
|
91
|
+
"next": "需要先补齐缺失的规划信息,或先解决仅规划阻塞项,再进入执行。",
|
|
92
|
+
"reasons": clarification_reasons,
|
|
93
|
+
"missing_fields": missing_fields,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
ready_command = "cgc-build" if flow == "feature" else "cgc-fix"
|
|
97
|
+
return {
|
|
98
|
+
"planning_status": "ready-for-build" if flow == "feature" else "ready-for-fix",
|
|
99
|
+
"recommended_command": to_public_command(ready_command),
|
|
100
|
+
"next": "当前待执行 step 已满足委派执行条件。",
|
|
101
|
+
"reasons": ["当前规划信息与 step 归属已经足以进入执行。"],
|
|
102
|
+
"missing_fields": [],
|
|
103
|
+
}
|