@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,588 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from codecgc_command_surface import to_public_command
|
|
6
|
+
|
|
7
|
+
EXPECTED_TOOL_BY_TARGET = {
|
|
8
|
+
"frontend": "implement_frontend_task",
|
|
9
|
+
"backend": "implement_backend_task",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
EXECUTE_COMMAND_BY_ARTIFACT = {
|
|
13
|
+
"feature": "cgc-build",
|
|
14
|
+
"issue": "cgc-fix",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
BOOLEAN_TEXT = {
|
|
18
|
+
True: "是",
|
|
19
|
+
False: "否",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
TARGET_LABELS = {
|
|
23
|
+
"frontend": "前端",
|
|
24
|
+
"backend": "后端",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
TOOL_LABELS = {
|
|
28
|
+
"implement_frontend_task": "前端执行器 / Gemini",
|
|
29
|
+
"implement_backend_task": "后端执行器 / Codex",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
MODE_LABELS = {
|
|
33
|
+
"dry-run": "仅预演",
|
|
34
|
+
"execute": "真实执行",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
OUTCOME_LABELS = {
|
|
38
|
+
"done": "完成",
|
|
39
|
+
"blocked": "受阻",
|
|
40
|
+
"failed": "失败",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
EVIDENCE_SOURCE_LABELS = {
|
|
44
|
+
"audit-result": "执行审计结果",
|
|
45
|
+
"workspace-diff": "工作区文件变更",
|
|
46
|
+
"workspace-snapshot": "工作区快照差异",
|
|
47
|
+
"workspace-diff-snapshot": "工作区快照差异",
|
|
48
|
+
"workspace-unified-diff-snapshot": "工作区统一 diff 证据",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
RISK_LABELS = {
|
|
52
|
+
"ownership-mismatch": "归属不匹配",
|
|
53
|
+
"out-of-scope-diff": "存在范围外变更",
|
|
54
|
+
"scope-mismatch": "范围不匹配",
|
|
55
|
+
"execution-not-performed": "未真实执行",
|
|
56
|
+
"executor-outcome-failed": "执行器结果失败",
|
|
57
|
+
"no-in-scope-diff": "未观察到范围内变更",
|
|
58
|
+
"missing-change-evidence": "缺少变更证据",
|
|
59
|
+
"reported-without-local-proof": "只有执行器上报,缺少本地证据",
|
|
60
|
+
"local-diff-unreported": "本地变更未被执行器上报",
|
|
61
|
+
"reported-local-mismatch": "执行器上报与本地证据不一致",
|
|
62
|
+
"diff-proof-missing": "缺少可核验的 diff 片段",
|
|
63
|
+
"diff-proof-nontext": "仅有非文本或不可读 diff 证据",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
EVIDENCE_CONFIDENCE_LABELS = {
|
|
67
|
+
"local-diff-out-of-scope": "本地 diff 存在范围外变更",
|
|
68
|
+
"local-diff-verified": "本地 diff 已验证",
|
|
69
|
+
"local-nontext-diff-verified": "本地非文本 diff 已验证",
|
|
70
|
+
"local-diff-partial-match": "本地 diff 与上报部分匹配",
|
|
71
|
+
"local-diff-mismatch": "本地 diff 与上报不匹配",
|
|
72
|
+
"local-diff-unreported": "本地 diff 未被上报",
|
|
73
|
+
"reported-without-local-proof": "只有上报,缺少本地证据",
|
|
74
|
+
"no-local-diff": "未观察到本地 diff",
|
|
75
|
+
"self-report-only": "仅执行器自报",
|
|
76
|
+
"stronger-than-self-report": "本地证据强于执行器自报",
|
|
77
|
+
"stronger-than-self-report-with-git-history": "本地证据已结合 git/history 增强",
|
|
78
|
+
"unknown": "未知",
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
POLICY_REASON_LABELS = {
|
|
82
|
+
"accepted-with-sufficient-evidence": "证据充分,允许通过",
|
|
83
|
+
"planning-boundary-risk": "存在规划边界风险",
|
|
84
|
+
"execution-not-performed": "尚未真实执行",
|
|
85
|
+
"execution-evidence-risk": "执行证据存在风险",
|
|
86
|
+
"unclassified-review-risk": "存在未分类的审核风险",
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
ACTION_KIND_LABELS = {
|
|
90
|
+
"close-step": "关闭当前执行步骤",
|
|
91
|
+
"repair-plan": "回到规划修正",
|
|
92
|
+
"execute-for-real": "执行一次真实运行",
|
|
93
|
+
"refine-and-rerun": "细化实现并重新执行",
|
|
94
|
+
"re-evaluate": "重新评估当前执行步骤",
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
FALLBACK_STAGE_LABELS = {
|
|
98
|
+
"closed": "已关闭",
|
|
99
|
+
"planning": "规划阶段",
|
|
100
|
+
"execution": "执行阶段",
|
|
101
|
+
"review": "审核阶段",
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
DECISION_LABELS = {
|
|
105
|
+
"accepted": "通过",
|
|
106
|
+
"changes-requested": "需修改",
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def normalize_path_text(path_text: str) -> str:
|
|
111
|
+
return path_text.replace("\\", "/").strip()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def unique_lines(items: list[str]) -> list[str]:
|
|
115
|
+
seen: set[str] = set()
|
|
116
|
+
result: list[str] = []
|
|
117
|
+
for item in items:
|
|
118
|
+
cleaned = item.strip()
|
|
119
|
+
if not cleaned or cleaned in seen:
|
|
120
|
+
continue
|
|
121
|
+
seen.add(cleaned)
|
|
122
|
+
result.append(cleaned)
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def bool_text(value: bool) -> str:
|
|
127
|
+
return BOOLEAN_TEXT[bool(value)]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def display_text(value: str, fallback: str = "未知") -> str:
|
|
131
|
+
cleaned = value.strip()
|
|
132
|
+
return cleaned or fallback
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def display_csv(items: list[str], fallback: str = "无") -> str:
|
|
136
|
+
cleaned = [item.strip() for item in items if item.strip()]
|
|
137
|
+
return ", ".join(cleaned) if cleaned else fallback
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def display_risk_classes(items: list[str]) -> str:
|
|
141
|
+
cleaned = [RISK_LABELS.get(item, item) for item in items if item.strip()]
|
|
142
|
+
return ", ".join(cleaned) if cleaned else "无"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def display_policy_reason(value: str) -> str:
|
|
146
|
+
cleaned = value.strip()
|
|
147
|
+
return POLICY_REASON_LABELS.get(cleaned, cleaned or "未知")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def display_decision(value: str) -> str:
|
|
151
|
+
cleaned = value.strip()
|
|
152
|
+
return DECISION_LABELS.get(cleaned, cleaned or "未知")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def display_evidence_confidence(value: str) -> str:
|
|
156
|
+
cleaned = value.strip()
|
|
157
|
+
return EVIDENCE_CONFIDENCE_LABELS.get(cleaned, cleaned or "未知")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def display_target(value: str) -> str:
|
|
161
|
+
cleaned = value.strip()
|
|
162
|
+
return TARGET_LABELS.get(cleaned, cleaned or "未知")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def display_tool_name(value: str) -> str:
|
|
166
|
+
cleaned = value.strip()
|
|
167
|
+
return TOOL_LABELS.get(cleaned, cleaned or "未知")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def display_mode(value: str) -> str:
|
|
171
|
+
cleaned = value.strip()
|
|
172
|
+
return MODE_LABELS.get(cleaned, cleaned or "未知")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def display_outcome(value: str) -> str:
|
|
176
|
+
cleaned = value.strip()
|
|
177
|
+
return OUTCOME_LABELS.get(cleaned, cleaned or "未知")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def display_evidence_source(value: str) -> str:
|
|
181
|
+
cleaned = value.strip()
|
|
182
|
+
return EVIDENCE_SOURCE_LABELS.get(cleaned, cleaned or "未知")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def classify_review_risk(
|
|
186
|
+
*,
|
|
187
|
+
ownership_ok: bool,
|
|
188
|
+
scope_ok: bool,
|
|
189
|
+
execution_performed: bool,
|
|
190
|
+
success_ok: bool,
|
|
191
|
+
change_evidence_ok: bool,
|
|
192
|
+
evidence_alignment_ok: bool,
|
|
193
|
+
local_evidence_available: bool,
|
|
194
|
+
out_of_scope_changed_files: list[str],
|
|
195
|
+
verified_changed_files: list[str],
|
|
196
|
+
workspace_changed_files: list[str],
|
|
197
|
+
reported_changed_files: list[str],
|
|
198
|
+
diff_proof_strong: bool,
|
|
199
|
+
diff_proof_nontext_only: bool,
|
|
200
|
+
) -> list[str]:
|
|
201
|
+
risk_classes: list[str] = []
|
|
202
|
+
|
|
203
|
+
if not ownership_ok:
|
|
204
|
+
risk_classes.append("ownership-mismatch")
|
|
205
|
+
if out_of_scope_changed_files:
|
|
206
|
+
risk_classes.append("out-of-scope-diff")
|
|
207
|
+
elif not scope_ok:
|
|
208
|
+
risk_classes.append("scope-mismatch")
|
|
209
|
+
if not execution_performed:
|
|
210
|
+
risk_classes.append("execution-not-performed")
|
|
211
|
+
if not success_ok:
|
|
212
|
+
risk_classes.append("executor-outcome-failed")
|
|
213
|
+
if local_evidence_available and workspace_changed_files and not verified_changed_files:
|
|
214
|
+
risk_classes.append("no-in-scope-diff")
|
|
215
|
+
elif not change_evidence_ok:
|
|
216
|
+
risk_classes.append("missing-change-evidence")
|
|
217
|
+
if local_evidence_available and verified_changed_files and not diff_proof_strong:
|
|
218
|
+
if not diff_proof_nontext_only:
|
|
219
|
+
risk_classes.append("diff-proof-missing")
|
|
220
|
+
if not evidence_alignment_ok:
|
|
221
|
+
if reported_changed_files and not workspace_changed_files:
|
|
222
|
+
risk_classes.append("reported-without-local-proof")
|
|
223
|
+
elif verified_changed_files and not reported_changed_files:
|
|
224
|
+
risk_classes.append("local-diff-unreported")
|
|
225
|
+
else:
|
|
226
|
+
risk_classes.append("reported-local-mismatch")
|
|
227
|
+
return risk_classes
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def resolve_review_policy(
|
|
231
|
+
*,
|
|
232
|
+
final_decision: str,
|
|
233
|
+
requested_decision: str,
|
|
234
|
+
next_step: str,
|
|
235
|
+
execute_command: str,
|
|
236
|
+
risk_classes: list[str],
|
|
237
|
+
) -> dict[str, str]:
|
|
238
|
+
if final_decision == "accepted":
|
|
239
|
+
return {
|
|
240
|
+
"recommended_command": "",
|
|
241
|
+
"resolved_next_step": next_step or "当前执行步骤已满足关闭条件,可以结束本轮工作流。",
|
|
242
|
+
"review_state": "accepted",
|
|
243
|
+
"action_kind": "close-step",
|
|
244
|
+
"fallback_stage": "closed",
|
|
245
|
+
"policy_reason": "accepted-with-sufficient-evidence",
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
planning_risks = {"ownership-mismatch", "out-of-scope-diff", "scope-mismatch"}
|
|
249
|
+
execution_retry_risks = {
|
|
250
|
+
"execution-not-performed",
|
|
251
|
+
"executor-outcome-failed",
|
|
252
|
+
"missing-change-evidence",
|
|
253
|
+
"diff-proof-missing",
|
|
254
|
+
"no-in-scope-diff",
|
|
255
|
+
"reported-without-local-proof",
|
|
256
|
+
"local-diff-unreported",
|
|
257
|
+
"reported-local-mismatch",
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if any(risk in planning_risks for risk in risk_classes):
|
|
261
|
+
return {
|
|
262
|
+
"recommended_command": "cgc-plan",
|
|
263
|
+
"resolved_next_step": next_step or "请先回到规划阶段,修正执行归属或目标路径范围后,再进入下一轮执行。",
|
|
264
|
+
"review_state": "returned-to-planning",
|
|
265
|
+
"action_kind": "repair-plan",
|
|
266
|
+
"fallback_stage": "planning",
|
|
267
|
+
"policy_reason": "planning-boundary-risk",
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if "execution-not-performed" in risk_classes:
|
|
271
|
+
return {
|
|
272
|
+
"recommended_command": execute_command,
|
|
273
|
+
"resolved_next_step": next_step or "请对同一范围的当前执行步骤进行一次真实执行,再重新申请审核。",
|
|
274
|
+
"review_state": "changes-requested",
|
|
275
|
+
"action_kind": "execute-for-real",
|
|
276
|
+
"fallback_stage": "execution",
|
|
277
|
+
"policy_reason": "execution-not-performed",
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if any(risk in execution_retry_risks for risk in risk_classes):
|
|
281
|
+
return {
|
|
282
|
+
"recommended_command": execute_command,
|
|
283
|
+
"resolved_next_step": next_step or "请细化当前实现,并在同一范围内重新执行当前步骤后,再重新申请审核。",
|
|
284
|
+
"review_state": "changes-requested",
|
|
285
|
+
"action_kind": "refine-and-rerun",
|
|
286
|
+
"fallback_stage": "execution",
|
|
287
|
+
"policy_reason": "execution-evidence-risk",
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
"recommended_command": execute_command if requested_decision == "accepted" else "",
|
|
292
|
+
"resolved_next_step": next_step or "请重新评估当前执行步骤,确认后再重新申请审核。",
|
|
293
|
+
"review_state": "changes-requested",
|
|
294
|
+
"action_kind": "re-evaluate",
|
|
295
|
+
"fallback_stage": "review",
|
|
296
|
+
"policy_reason": "unclassified-review-risk",
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def classify_evidence_confidence(
|
|
301
|
+
*,
|
|
302
|
+
evidence_source: str,
|
|
303
|
+
reported_changed_files: list[str],
|
|
304
|
+
workspace_changed_files: list[str],
|
|
305
|
+
verified_changed_files: list[str],
|
|
306
|
+
out_of_scope_changed_files: list[str],
|
|
307
|
+
file_diffs: list[dict[str, Any]],
|
|
308
|
+
) -> tuple[str, bool, bool]:
|
|
309
|
+
local_evidence_available = bool(
|
|
310
|
+
workspace_changed_files or verified_changed_files or out_of_scope_changed_files or file_diffs
|
|
311
|
+
)
|
|
312
|
+
reported = set(reported_changed_files)
|
|
313
|
+
observed = set(workspace_changed_files)
|
|
314
|
+
verified = set(verified_changed_files)
|
|
315
|
+
|
|
316
|
+
if local_evidence_available:
|
|
317
|
+
diff_kinds = {
|
|
318
|
+
str(item.get("diff_kind", "")).strip()
|
|
319
|
+
for item in file_diffs
|
|
320
|
+
if isinstance(item, dict)
|
|
321
|
+
}
|
|
322
|
+
if out_of_scope_changed_files:
|
|
323
|
+
return "local-diff-out-of-scope", False, True
|
|
324
|
+
if verified and diff_kinds and diff_kinds <= {"binary-or-unreadable"}:
|
|
325
|
+
return "local-nontext-diff-verified", True, True
|
|
326
|
+
if verified and reported:
|
|
327
|
+
if verified == reported:
|
|
328
|
+
return "local-diff-verified", True, True
|
|
329
|
+
if verified.issubset(reported) or reported.issubset(verified):
|
|
330
|
+
return "local-diff-partial-match", True, True
|
|
331
|
+
return "local-diff-mismatch", False, True
|
|
332
|
+
if verified and not reported:
|
|
333
|
+
return "local-diff-unreported", False, True
|
|
334
|
+
if reported and not observed:
|
|
335
|
+
return "reported-without-local-proof", False, True
|
|
336
|
+
if observed and not verified:
|
|
337
|
+
return "local-diff-out-of-scope", False, True
|
|
338
|
+
return "no-local-diff", False, True
|
|
339
|
+
|
|
340
|
+
if evidence_source == "audit-result" and reported:
|
|
341
|
+
return "self-report-only", True, False
|
|
342
|
+
return "no-local-diff", False, False
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def evaluate_review(audit: dict[str, Any], requested_decision: str, risks: list[str], next_step: str) -> dict[str, Any]:
|
|
346
|
+
result = audit.get("result", {}) if isinstance(audit.get("result"), dict) else {}
|
|
347
|
+
source = audit.get("source", {}) if isinstance(audit.get("source"), dict) else {}
|
|
348
|
+
file_evidence = audit.get("file_evidence", {}) if isinstance(audit.get("file_evidence"), dict) else {}
|
|
349
|
+
|
|
350
|
+
artifact_type = str(source.get("artifact_type", ""))
|
|
351
|
+
execute_command = EXECUTE_COMMAND_BY_ARTIFACT.get(artifact_type, "")
|
|
352
|
+
target = str(audit.get("target", ""))
|
|
353
|
+
tool_name = str(audit.get("tool_name", ""))
|
|
354
|
+
mode = str(audit.get("mode", ""))
|
|
355
|
+
outcome = str(result.get("outcome", ""))
|
|
356
|
+
success = bool(result.get("success"))
|
|
357
|
+
|
|
358
|
+
target_paths = [normalize_path_text(str(path)) for path in audit.get("target_paths", [])]
|
|
359
|
+
changed_files = [normalize_path_text(str(path)) for path in result.get("changed_files", [])]
|
|
360
|
+
workspace_changed_files = [
|
|
361
|
+
normalize_path_text(str(path)) for path in file_evidence.get("workspace_changed_files", [])
|
|
362
|
+
]
|
|
363
|
+
verified_changed_files = [
|
|
364
|
+
normalize_path_text(str(path)) for path in file_evidence.get("verified_changed_files", [])
|
|
365
|
+
]
|
|
366
|
+
out_of_scope_changed_files = [
|
|
367
|
+
normalize_path_text(str(path)) for path in file_evidence.get("out_of_scope_changed_files", [])
|
|
368
|
+
]
|
|
369
|
+
file_diffs = file_evidence.get("file_diffs", []) if isinstance(file_evidence.get("file_diffs"), list) else []
|
|
370
|
+
git_evidence = file_evidence.get("git_evidence", {}) if isinstance(file_evidence.get("git_evidence"), dict) else {}
|
|
371
|
+
policy_checks = [str(item) for item in result.get("policy_checks", [])]
|
|
372
|
+
result_risks = [str(item) for item in result.get("risks", [])]
|
|
373
|
+
acceptance_criteria = [str(item) for item in audit.get("acceptance_criteria", [])]
|
|
374
|
+
evidence_source = str(file_evidence.get("evidence_source", "audit-result"))
|
|
375
|
+
stored_evidence_confidence = str(file_evidence.get("evidence_confidence", "unknown"))
|
|
376
|
+
git_repository_detected = bool(git_evidence.get("git_repository_detected"))
|
|
377
|
+
git_history_available = bool(git_evidence.get("history_available"))
|
|
378
|
+
git_status = str(git_evidence.get("status", "")).strip() or "unknown"
|
|
379
|
+
tracked_changed_files = [
|
|
380
|
+
normalize_path_text(str(path)) for path in git_evidence.get("tracked_changed_files", [])
|
|
381
|
+
]
|
|
382
|
+
untracked_changed_files = [
|
|
383
|
+
normalize_path_text(str(path)) for path in git_evidence.get("untracked_changed_files", [])
|
|
384
|
+
]
|
|
385
|
+
git_changed_files = git_evidence.get("git_changed_files", []) if isinstance(git_evidence.get("git_changed_files"), list) else []
|
|
386
|
+
|
|
387
|
+
diff_excerpt_lines: list[str] = []
|
|
388
|
+
in_scope_diff_items: list[dict[str, Any]] = []
|
|
389
|
+
for item in file_diffs:
|
|
390
|
+
if not isinstance(item, dict):
|
|
391
|
+
continue
|
|
392
|
+
path_text = str(item.get("path", "")).strip() or "未知文件"
|
|
393
|
+
scope_match_kind = str(item.get("scope_match_kind", "")).strip() or "unknown"
|
|
394
|
+
diff_excerpt = str(item.get("diff_excerpt", "")).strip()
|
|
395
|
+
if bool(item.get("in_scope")):
|
|
396
|
+
in_scope_diff_items.append(item)
|
|
397
|
+
if diff_excerpt:
|
|
398
|
+
diff_excerpt_lines.append(
|
|
399
|
+
f"[{path_text} | {scope_match_kind}]\n{diff_excerpt}"
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
evidence_confidence, evidence_alignment_ok, local_evidence_available = classify_evidence_confidence(
|
|
403
|
+
evidence_source=evidence_source,
|
|
404
|
+
reported_changed_files=changed_files,
|
|
405
|
+
workspace_changed_files=workspace_changed_files,
|
|
406
|
+
verified_changed_files=verified_changed_files,
|
|
407
|
+
out_of_scope_changed_files=out_of_scope_changed_files,
|
|
408
|
+
file_diffs=file_diffs,
|
|
409
|
+
)
|
|
410
|
+
if stored_evidence_confidence not in {"", "unknown"} and evidence_confidence == "self-report-only":
|
|
411
|
+
evidence_confidence = stored_evidence_confidence
|
|
412
|
+
|
|
413
|
+
expected_tool = EXPECTED_TOOL_BY_TARGET.get(target, "")
|
|
414
|
+
ownership_ok = bool(expected_tool) and tool_name == expected_tool
|
|
415
|
+
if local_evidence_available:
|
|
416
|
+
effective_changed_files = verified_changed_files
|
|
417
|
+
scope_ok = not out_of_scope_changed_files
|
|
418
|
+
else:
|
|
419
|
+
effective_changed_files = changed_files
|
|
420
|
+
scope_ok = all(path in target_paths for path in changed_files)
|
|
421
|
+
diff_proof_strong = bool(
|
|
422
|
+
[
|
|
423
|
+
item for item in in_scope_diff_items
|
|
424
|
+
if str(item.get("diff_kind", "")).strip() == "unified-text-diff"
|
|
425
|
+
and int(item.get("changed_line_count", 0) or 0) > 0
|
|
426
|
+
]
|
|
427
|
+
)
|
|
428
|
+
diff_proof_nontext_only = bool(in_scope_diff_items) and all(
|
|
429
|
+
str(item.get("diff_kind", "")).strip() == "binary-or-unreadable"
|
|
430
|
+
for item in in_scope_diff_items
|
|
431
|
+
)
|
|
432
|
+
execution_performed = mode != "dry-run" and "dry_run_only" not in policy_checks and "execution_not_performed" not in result_risks
|
|
433
|
+
success_ok = success and outcome == "done"
|
|
434
|
+
|
|
435
|
+
failure_reasons: list[str] = []
|
|
436
|
+
if not ownership_ok:
|
|
437
|
+
failure_reasons.append("执行器归属校验未通过")
|
|
438
|
+
if not scope_ok:
|
|
439
|
+
failure_reasons.append("变更文件超出了 target_paths 范围")
|
|
440
|
+
if local_evidence_available and not effective_changed_files:
|
|
441
|
+
failure_reasons.append("未观察到已验证的范围内文件变更")
|
|
442
|
+
if local_evidence_available and effective_changed_files and not diff_proof_strong:
|
|
443
|
+
if not diff_proof_nontext_only:
|
|
444
|
+
failure_reasons.append("当前只有文件级变化,缺少可核验的统一 diff 片段")
|
|
445
|
+
if not evidence_alignment_ok:
|
|
446
|
+
failure_reasons.append("执行器自报结果与本地文件证据不一致")
|
|
447
|
+
if not execution_performed:
|
|
448
|
+
failure_reasons.append("本次仅进行了预演执行,或尚未发生真实执行")
|
|
449
|
+
if not success_ok:
|
|
450
|
+
failure_reasons.append("执行器未返回成功完成结果")
|
|
451
|
+
|
|
452
|
+
change_evidence_ok = bool(effective_changed_files) or not bool(target_paths)
|
|
453
|
+
risk_classes = classify_review_risk(
|
|
454
|
+
ownership_ok=ownership_ok,
|
|
455
|
+
scope_ok=scope_ok,
|
|
456
|
+
execution_performed=execution_performed,
|
|
457
|
+
success_ok=success_ok,
|
|
458
|
+
change_evidence_ok=change_evidence_ok,
|
|
459
|
+
evidence_alignment_ok=evidence_alignment_ok,
|
|
460
|
+
local_evidence_available=local_evidence_available,
|
|
461
|
+
out_of_scope_changed_files=out_of_scope_changed_files,
|
|
462
|
+
verified_changed_files=effective_changed_files,
|
|
463
|
+
workspace_changed_files=workspace_changed_files,
|
|
464
|
+
reported_changed_files=changed_files,
|
|
465
|
+
diff_proof_strong=diff_proof_strong,
|
|
466
|
+
diff_proof_nontext_only=diff_proof_nontext_only,
|
|
467
|
+
)
|
|
468
|
+
ready_for_acceptance = (
|
|
469
|
+
ownership_ok
|
|
470
|
+
and scope_ok
|
|
471
|
+
and execution_performed
|
|
472
|
+
and success_ok
|
|
473
|
+
and change_evidence_ok
|
|
474
|
+
and evidence_alignment_ok
|
|
475
|
+
and (not local_evidence_available or diff_proof_strong or diff_proof_nontext_only)
|
|
476
|
+
)
|
|
477
|
+
final_decision = "accepted" if requested_decision == "accepted" and ready_for_acceptance else "changes-requested"
|
|
478
|
+
resolution = resolve_review_policy(
|
|
479
|
+
final_decision=final_decision,
|
|
480
|
+
requested_decision=requested_decision,
|
|
481
|
+
next_step=next_step,
|
|
482
|
+
execute_command=execute_command,
|
|
483
|
+
risk_classes=risk_classes,
|
|
484
|
+
)
|
|
485
|
+
recommended_command = resolution["recommended_command"]
|
|
486
|
+
resolved_next_step = resolution["resolved_next_step"]
|
|
487
|
+
review_state = resolution["review_state"]
|
|
488
|
+
|
|
489
|
+
merged_risks = unique_lines([*risks, *result_risks, *failure_reasons])
|
|
490
|
+
|
|
491
|
+
scope_lines = [
|
|
492
|
+
f"请求决策: {display_decision(requested_decision)}",
|
|
493
|
+
f"最终决策: {display_decision(final_decision)}",
|
|
494
|
+
f"执行结果: {display_outcome(outcome)}",
|
|
495
|
+
f"证据来源: {display_evidence_source(evidence_source)}",
|
|
496
|
+
f"风险分类: {display_risk_classes(risk_classes)}",
|
|
497
|
+
f"回退阶段: {FALLBACK_STAGE_LABELS.get(resolution['fallback_stage'], resolution['fallback_stage'])}",
|
|
498
|
+
f"策略原因: {display_policy_reason(resolution['policy_reason'])}",
|
|
499
|
+
f"范围是否满足: {bool_text(scope_ok)}",
|
|
500
|
+
f"变更文件是否落在 target_paths 内: {bool_text(scope_ok)}",
|
|
501
|
+
]
|
|
502
|
+
|
|
503
|
+
executor_lines = [
|
|
504
|
+
f"执行器目标: {display_target(target)}",
|
|
505
|
+
f"预期工具: {display_tool_name(expected_tool)}",
|
|
506
|
+
f"实际工具: {display_tool_name(tool_name)}",
|
|
507
|
+
f"归属是否满足: {bool_text(ownership_ok)}",
|
|
508
|
+
f"执行模式: {display_mode(mode)}",
|
|
509
|
+
f"是否真实执行: {bool_text(execution_performed)}",
|
|
510
|
+
f"策略检查项: {display_csv(policy_checks)}",
|
|
511
|
+
]
|
|
512
|
+
|
|
513
|
+
verification_lines = [
|
|
514
|
+
f"摘要: {display_text(str(result.get('summary', '') or ''), '无')}",
|
|
515
|
+
f"证据置信度: {display_evidence_confidence(evidence_confidence)}",
|
|
516
|
+
f"diff 证据是否足够强: {bool_text(diff_proof_strong)}",
|
|
517
|
+
f"是否属于非文本 diff 证据: {bool_text(diff_proof_nontext_only)}",
|
|
518
|
+
f"是否有本地证据: {bool_text(local_evidence_available)}",
|
|
519
|
+
f"是否检测到 git 仓库: {bool_text(git_repository_detected)}",
|
|
520
|
+
f"git/history 证据是否可用: {bool_text(git_history_available)}",
|
|
521
|
+
f"git 证据状态: {display_text(git_status, '无')}",
|
|
522
|
+
f"执行器上报与本地证据是否一致: {bool_text(evidence_alignment_ok)}",
|
|
523
|
+
f"执行器上报的变更文件: {display_csv(changed_files)}",
|
|
524
|
+
f"工作区变更文件: {display_csv(workspace_changed_files)}",
|
|
525
|
+
f"已验证的范围内变更文件: {display_csv(effective_changed_files)}",
|
|
526
|
+
f"范围外变更文件: {display_csv(out_of_scope_changed_files)}",
|
|
527
|
+
f"git 已跟踪变更文件: {display_csv(tracked_changed_files)}",
|
|
528
|
+
f"git 未跟踪变更文件: {display_csv(untracked_changed_files)}",
|
|
529
|
+
"git 历史摘要: "
|
|
530
|
+
+ (
|
|
531
|
+
" | ".join(
|
|
532
|
+
f"{item.get('path', '未知')}:{'tracked' if item.get('tracked') else 'untracked'}"
|
|
533
|
+
+ (
|
|
534
|
+
f":{str(item.get('last_commit_hash', ''))[:10]}"
|
|
535
|
+
if str(item.get("last_commit_hash", "")).strip()
|
|
536
|
+
else ""
|
|
537
|
+
)
|
|
538
|
+
+ (
|
|
539
|
+
f":{str(item.get('last_commit_subject', '')).strip()}"
|
|
540
|
+
if str(item.get("last_commit_subject", "")).strip()
|
|
541
|
+
else ""
|
|
542
|
+
)
|
|
543
|
+
for item in git_changed_files
|
|
544
|
+
if isinstance(item, dict)
|
|
545
|
+
)
|
|
546
|
+
or "无"
|
|
547
|
+
),
|
|
548
|
+
"观测到的文件 diff: "
|
|
549
|
+
+ (
|
|
550
|
+
" | ".join(
|
|
551
|
+
f"{item.get('path', '未知')}:{item.get('change_type', 'changed')}:{item.get('scope_match_kind', 'unknown')}"
|
|
552
|
+
for item in file_diffs
|
|
553
|
+
)
|
|
554
|
+
or "无"
|
|
555
|
+
),
|
|
556
|
+
"diff 证据类型: "
|
|
557
|
+
+ (
|
|
558
|
+
" | ".join(
|
|
559
|
+
f"{item.get('path', '未知')}:{item.get('diff_kind', 'unknown')}:{item.get('changed_line_count', 0)}"
|
|
560
|
+
for item in file_diffs
|
|
561
|
+
)
|
|
562
|
+
or "无"
|
|
563
|
+
),
|
|
564
|
+
"观测到的统一 diff 片段: "
|
|
565
|
+
+ ("\n\n".join(diff_excerpt_lines) if diff_excerpt_lines else "无"),
|
|
566
|
+
f"验收条件: {' | '.join(acceptance_criteria) or '无'}",
|
|
567
|
+
]
|
|
568
|
+
|
|
569
|
+
return {
|
|
570
|
+
"requested_decision": requested_decision,
|
|
571
|
+
"final_decision": final_decision,
|
|
572
|
+
"accepted": final_decision == "accepted",
|
|
573
|
+
"review_state": review_state,
|
|
574
|
+
"recommended_command": to_public_command(recommended_command),
|
|
575
|
+
"next_step": resolved_next_step,
|
|
576
|
+
"recommended_action_kind": resolution["action_kind"],
|
|
577
|
+
"fallback_stage": resolution["fallback_stage"],
|
|
578
|
+
"policy_reason": resolution["policy_reason"],
|
|
579
|
+
"reviewed_task_id": str(result.get("task_id", "")),
|
|
580
|
+
"reviewed_step_number": int(source.get("step_number", 0) or 0),
|
|
581
|
+
"scope_check": "\n".join(f"- {line}" for line in scope_lines),
|
|
582
|
+
"executor_check": "\n".join(f"- {line}" for line in executor_lines),
|
|
583
|
+
"verification": "\n".join(f"- {line}" for line in verification_lines),
|
|
584
|
+
"remaining_risk": "\n".join(f"- {line}" for line in merged_risks) if merged_risks else "- 无",
|
|
585
|
+
"failure_reasons": failure_reasons,
|
|
586
|
+
"risk_classes": risk_classes,
|
|
587
|
+
"evidence_confidence": evidence_confidence,
|
|
588
|
+
}
|