@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.
Files changed (128) hide show
  1. package/.claude/hooks/route-edit.ps1 +86 -0
  2. package/INSTALLATION.md +550 -0
  3. package/LICENSE +21 -0
  4. package/README.md +171 -0
  5. package/bin/cgc-build.js +4 -0
  6. package/bin/cgc-doctor.js +4 -0
  7. package/bin/cgc-entry.js +4 -0
  8. package/bin/cgc-external-audit.js +4 -0
  9. package/bin/cgc-fix.js +4 -0
  10. package/bin/cgc-history.js +4 -0
  11. package/bin/cgc-install.js +4 -0
  12. package/bin/cgc-lifecycle.js +4 -0
  13. package/bin/cgc-package-audit.js +4 -0
  14. package/bin/cgc-plan.js +4 -0
  15. package/bin/cgc-release-readiness.js +4 -0
  16. package/bin/cgc-review.js +4 -0
  17. package/bin/cgc-route.js +4 -0
  18. package/bin/cgc-status.js +4 -0
  19. package/bin/cgc-test.js +4 -0
  20. package/bin/cgc.js +4 -0
  21. package/bin/codecgc.js +1284 -0
  22. package/codecgc/cgc/SKILL.md +46 -0
  23. package/codecgc/cgc-arch/SKILL.md +61 -0
  24. package/codecgc/cgc-build/SKILL.md +53 -0
  25. package/codecgc/cgc-decide/SKILL.md +55 -0
  26. package/codecgc/cgc-fix/SKILL.md +47 -0
  27. package/codecgc/cgc-learn/SKILL.md +46 -0
  28. package/codecgc/cgc-onboard/SKILL.md +52 -0
  29. package/codecgc/cgc-plan/SKILL.md +48 -0
  30. package/codecgc/cgc-refactor/SKILL.md +46 -0
  31. package/codecgc/cgc-req/SKILL.md +61 -0
  32. package/codecgc/cgc-review/SKILL.md +57 -0
  33. package/codecgc/cgc-roadmap/SKILL.md +55 -0
  34. package/codecgc/cgc-test/SKILL.md +21 -0
  35. package/codecgc/reference/api-cgc-review-libdoc.md +13 -0
  36. package/codecgc/reference/artifact-class-policy.md +81 -0
  37. package/codecgc/reference/build-flow.md +95 -0
  38. package/codecgc/reference/checklist-contract.md +103 -0
  39. package/codecgc/reference/execution-audit.md +121 -0
  40. package/codecgc/reference/execution-model.md +118 -0
  41. package/codecgc/reference/execution-routing.md +130 -0
  42. package/codecgc/reference/executor-contract.md +87 -0
  43. package/codecgc/reference/external-capability-registry.json +104 -0
  44. package/codecgc/reference/fix-flow.md +94 -0
  45. package/codecgc/reference/fixture-governance.md +60 -0
  46. package/codecgc/reference/flow-execution.md +65 -0
  47. package/codecgc/reference/lifecycle-map.md +172 -0
  48. package/codecgc/reference/lifecycle-playbook.md +104 -0
  49. package/codecgc/reference/long-lived-artifacts.md +98 -0
  50. package/codecgc/reference/operation-guide.md +242 -0
  51. package/codecgc/reference/release-maintenance-playbook.md +150 -0
  52. package/codecgc/reference/review-writeback.md +141 -0
  53. package/codecgc/reference/role-model.md +128 -0
  54. package/codecgc/reference/runtime-boundary.md +72 -0
  55. package/codecgc/reference/shared-conventions.md +93 -0
  56. package/codecgc/reference/workflow-scaffold.md +57 -0
  57. package/codexmcp/LICENSE +21 -0
  58. package/codexmcp/README.md +294 -0
  59. package/codexmcp/pyproject.toml +37 -0
  60. package/codexmcp/src/codexmcp/__init__.py +4 -0
  61. package/codexmcp/src/codexmcp/cli.py +12 -0
  62. package/codexmcp/src/codexmcp/server.py +529 -0
  63. package/geminimcp/README.md +258 -0
  64. package/geminimcp/pyproject.toml +15 -0
  65. package/geminimcp/src/geminimcp/__init__.py +4 -0
  66. package/geminimcp/src/geminimcp/cli.py +12 -0
  67. package/geminimcp/src/geminimcp/server.py +465 -0
  68. package/model-routing.yaml +30 -0
  69. package/package.json +90 -0
  70. package/requirements.txt +1 -0
  71. package/scripts/README-codecgc-cli.md +89 -0
  72. package/scripts/audit_codecgc_external_capabilities.py +276 -0
  73. package/scripts/audit_codecgc_historical_audits.py +242 -0
  74. package/scripts/audit_codecgc_lifecycle.py +241 -0
  75. package/scripts/audit_codecgc_package_runtime.py +445 -0
  76. package/scripts/audit_codecgc_release_readiness.py +202 -0
  77. package/scripts/audit_codecgc_review_policy.py +82 -0
  78. package/scripts/audit_codecgc_workflow_history.py +317 -0
  79. package/scripts/build_codecgc_task.py +487 -0
  80. package/scripts/codecgc_artifact_roots.py +40 -0
  81. package/scripts/codecgc_cli.py +843 -0
  82. package/scripts/codecgc_command_surface.py +28 -0
  83. package/scripts/codecgc_console_io.py +45 -0
  84. package/scripts/codecgc_executor_registry.py +54 -0
  85. package/scripts/codecgc_file_evidence.py +349 -0
  86. package/scripts/codecgc_flow_control.py +233 -0
  87. package/scripts/codecgc_governance_dedupe.py +161 -0
  88. package/scripts/codecgc_plan_decision.py +103 -0
  89. package/scripts/codecgc_review_control.py +588 -0
  90. package/scripts/codecgc_roadmap_templates.py +149 -0
  91. package/scripts/codecgc_routing_paths.py +16 -0
  92. package/scripts/codecgc_routing_template.py +135 -0
  93. package/scripts/codecgc_runtime_paths.py +22 -0
  94. package/scripts/codecgc_session_recovery.py +44 -0
  95. package/scripts/codecgc_step_control.py +154 -0
  96. package/scripts/codecgc_workflow_runtime.py +63 -0
  97. package/scripts/codecgc_workflow_templates.py +437 -0
  98. package/scripts/entry_codecgc_workflow.py +3419 -0
  99. package/scripts/exercise_mcp_tools.py +109 -0
  100. package/scripts/expand_codecgc_roadmap.py +664 -0
  101. package/scripts/init_codecgc_roadmap.py +134 -0
  102. package/scripts/init_codecgc_workflow.py +207 -0
  103. package/scripts/install_codecgc.py +938 -0
  104. package/scripts/migrate_demo_workflows_to_fixtures.py +128 -0
  105. package/scripts/normalize_codecgc_audits.py +114 -0
  106. package/scripts/normalize_codecgc_governance_docs.py +79 -0
  107. package/scripts/normalize_codecgc_workflow_docs.py +269 -0
  108. package/scripts/plan_codecgc_workflow.py +970 -0
  109. package/scripts/refresh_codecgc_review_policy.py +223 -0
  110. package/scripts/review_codecgc_workflow.py +88 -0
  111. package/scripts/route_codecgc_workflow.py +671 -0
  112. package/scripts/run_codecgc_build.py +104 -0
  113. package/scripts/run_codecgc_fix.py +104 -0
  114. package/scripts/run_codecgc_flow_step.py +165 -0
  115. package/scripts/run_codecgc_task.py +410 -0
  116. package/scripts/run_codecgc_test.py +105 -0
  117. package/scripts/sync_codecgc_mcp_config.py +41 -0
  118. package/scripts/write_codecgc_architecture.py +78 -0
  119. package/scripts/write_codecgc_decision.py +83 -0
  120. package/scripts/write_codecgc_explore.py +118 -0
  121. package/scripts/write_codecgc_guide.py +141 -0
  122. package/scripts/write_codecgc_learning.py +87 -0
  123. package/scripts/write_codecgc_libdoc.py +140 -0
  124. package/scripts/write_codecgc_refactor.py +78 -0
  125. package/scripts/write_codecgc_requirement.py +78 -0
  126. package/scripts/write_codecgc_review.py +291 -0
  127. package/scripts/write_codecgc_roadmap.py +122 -0
  128. 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
+ }