code-mower 0.5.0b5__py3-none-any.whl
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.
- code_mower/__init__.py +3 -0
- code_mower/adapters/__init__.py +39 -0
- code_mower/adapters/_base.py +148 -0
- code_mower/adapters/cursor_bugbot.py +97 -0
- code_mower/adapters/gitar.py +111 -0
- code_mower/adapters/greptile.py +191 -0
- code_mower/adapters/qodo.py +140 -0
- code_mower/antigravity_cli_audit_pr.py +241 -0
- code_mower/audit_handoff_log.py +345 -0
- code_mower/audit_labeler_lib.py +311 -0
- code_mower/audit_progress.py +203 -0
- code_mower/blind_review_artifacts.py +562 -0
- code_mower/blind_review_coordinator.py +276 -0
- code_mower/bootstrap.py +524 -0
- code_mower/builder_experiment.py +539 -0
- code_mower/calibration/__init__.py +181 -0
- code_mower/calibration/arms.py +267 -0
- code_mower/calibration/auto_discovery.py +348 -0
- code_mower/calibration/commands.py +192 -0
- code_mower/calibration/context_inputs.py +199 -0
- code_mower/calibration/corpus.py +91 -0
- code_mower/calibration/evidence.py +20 -0
- code_mower/calibration/evidence_report.py +360 -0
- code_mower/calibration/identity.py +27 -0
- code_mower/calibration/metrics.py +18 -0
- code_mower/calibration/overlap.py +103 -0
- code_mower/calibration/planning.py +309 -0
- code_mower/calibration/policy.py +207 -0
- code_mower/calibration/results.py +315 -0
- code_mower/calibration/run_results.py +147 -0
- code_mower/calibration/run_status.py +64 -0
- code_mower/calibration/runner.py +360 -0
- code_mower/calibration/truth.py +188 -0
- code_mower/calibration/value_report.py +142 -0
- code_mower/checks.py +402 -0
- code_mower/claude_audit_pr.py +1126 -0
- code_mower/claude_cli_bounce.py +303 -0
- code_mower/claude_cli_environment.py +73 -0
- code_mower/clear_stale.py +374 -0
- code_mower/cli.py +537 -0
- code_mower/cloud.py +674 -0
- code_mower/cloud_client/__init__.py +173 -0
- code_mower/cloud_client/bundle.py +155 -0
- code_mower/cloud_client/doctor.py +206 -0
- code_mower/cloud_client/dogfood.py +78 -0
- code_mower/cloud_client/endpoints.py +114 -0
- code_mower/cloud_client/errors.py +7 -0
- code_mower/cloud_client/events.py +278 -0
- code_mower/cloud_client/export.py +272 -0
- code_mower/cloud_client/git_metadata.py +46 -0
- code_mower/cloud_client/manifest.py +39 -0
- code_mower/cloud_client/operations.py +448 -0
- code_mower/cloud_client/reports.py +45 -0
- code_mower/cloud_client/setup.py +205 -0
- code_mower/cloud_client/upload.py +97 -0
- code_mower/code_mower_calibration.py +598 -0
- code_mower/code_mower_context_packs.py +591 -0
- code_mower/code_mower_merge.py +227 -0
- code_mower/code_mower_telemetry.py +561 -0
- code_mower/coderabbit_cli_audit_pr.py +526 -0
- code_mower/codex_audit_env_preflight.py +220 -0
- code_mower/codex_audit_pr.py +1738 -0
- code_mower/codex_audit_schema_smoke.py +160 -0
- code_mower/codex_audit_verdict.schema.json +44 -0
- code_mower/config.py +655 -0
- code_mower/doctor.py +161 -0
- code_mower/doctor_checks/__init__.py +104 -0
- code_mower/doctor_checks/cloud.py +129 -0
- code_mower/doctor_checks/common.py +215 -0
- code_mower/doctor_checks/github.py +128 -0
- code_mower/doctor_checks/github_actions.py +11 -0
- code_mower/doctor_checks/github_actions_cost.py +99 -0
- code_mower/doctor_checks/github_actions_cost_summary.py +111 -0
- code_mower/doctor_checks/github_actions_failure_annotations.py +27 -0
- code_mower/doctor_checks/github_actions_failure_models.py +47 -0
- code_mower/doctor_checks/github_actions_failure_scan.py +200 -0
- code_mower/doctor_checks/github_actions_failure_selection.py +63 -0
- code_mower/doctor_checks/github_actions_failures.py +103 -0
- code_mower/doctor_checks/github_actions_permissions.py +55 -0
- code_mower/doctor_checks/github_api.py +79 -0
- code_mower/doctor_checks/github_branch.py +56 -0
- code_mower/doctor_checks/github_config.py +25 -0
- code_mower/doctor_checks/github_provider.py +61 -0
- code_mower/doctor_checks/github_repo.py +120 -0
- code_mower/doctor_checks/groups.py +36 -0
- code_mower/doctor_checks/models.py +96 -0
- code_mower/doctor_checks/output.py +86 -0
- code_mower/doctor_checks/presets.py +64 -0
- code_mower/doctor_checks/privacy.py +20 -0
- code_mower/doctor_checks/provider_api_model.py +138 -0
- code_mower/doctor_checks/provider_api_model_openai.py +29 -0
- code_mower/doctor_checks/provider_api_model_profiles.py +137 -0
- code_mower/doctor_checks/provider_env.py +113 -0
- code_mower/doctor_checks/provider_env_required.py +56 -0
- code_mower/doctor_checks/provider_env_tokens.py +100 -0
- code_mower/doctor_checks/provider_local_cli.py +162 -0
- code_mower/doctor_checks/provider_local_cli_commands.py +47 -0
- code_mower/doctor_checks/provider_local_cli_probe_config.py +70 -0
- code_mower/doctor_checks/provider_probe.py +20 -0
- code_mower/doctor_checks/provider_probe_auth.py +52 -0
- code_mower/doctor_checks/provider_probe_evaluation.py +109 -0
- code_mower/doctor_checks/provider_probe_json.py +45 -0
- code_mower/doctor_checks/provider_probe_remediation.py +39 -0
- code_mower/doctor_checks/providers.py +159 -0
- code_mower/doctor_checks/registry.py +69 -0
- code_mower/doctor_checks/runner.py +188 -0
- code_mower/doctor_checks/runtime.py +89 -0
- code_mower/doctor_checks/runtime_github_auth.py +148 -0
- code_mower/gemini_cli_audit_pr.py +897 -0
- code_mower/hermes_cli_audit_pr.py +436 -0
- code_mower/init.py +888 -0
- code_mower/lane_configs/__init__.py +37 -0
- code_mower/lane_configs/aider.py +32 -0
- code_mower/lane_configs/antigravity_cli.py +35 -0
- code_mower/lane_configs/claude.py +35 -0
- code_mower/lane_configs/codex.py +32 -0
- code_mower/lane_configs/devin.py +33 -0
- code_mower/lane_configs/gemini_cli.py +35 -0
- code_mower/lane_configs/hermes_cli.py +35 -0
- code_mower/lane_configs/local_llm.py +31 -0
- code_mower/local_llm_audit_pr.py +1364 -0
- code_mower/local_llm_bakeoff.py +458 -0
- code_mower/local_llm_calibration.py +441 -0
- code_mower/local_llm_profiles.py +66 -0
- code_mower/migration.py +508 -0
- code_mower/migration_install.py +292 -0
- code_mower/migration_mirror.py +392 -0
- code_mower/migration_readiness.py +237 -0
- code_mower/migration_rehearsal.py +718 -0
- code_mower/next_steps.py +441 -0
- code_mower/package.py +673 -0
- code_mower/package_content.py +444 -0
- code_mower/package_manifest.py +452 -0
- code_mower/package_paths.py +53 -0
- code_mower/package_rendering.py +90 -0
- code_mower/package_static.py +585 -0
- code_mower/prompts.py +267 -0
- code_mower/provider_registry.py +469 -0
- code_mower/provider_runners/__init__.py +60 -0
- code_mower/provider_runners/comments.py +31 -0
- code_mower/provider_runners/git.py +46 -0
- code_mower/provider_runners/github_auth.py +61 -0
- code_mower/provider_runners/github_pr.py +120 -0
- code_mower/provider_runners/process.py +58 -0
- code_mower/provider_runners/repo_paths.py +23 -0
- code_mower/provider_runners/text_schema.py +41 -0
- code_mower/provider_runners/verdict_artifacts.py +103 -0
- code_mower/provider_runners/workspace.py +57 -0
- code_mower/release_readiness.py +549 -0
- code_mower/reviewer_metrics.py +389 -0
- code_mower/saas_reviewer_labeler.py +809 -0
- code_mower/secrets.py +89 -0
- code_mower/templates/builder-experiment.example.json +55 -0
- code_mower/templates/calibration-corpus.example.json +129 -0
- code_mower/templates/calibration-corpus.json +129 -0
- code_mower/templates/code-mower.example.yml +423 -0
- code_mower/templates/context-packs.example.json +150 -0
- code_mower/templates/lane_prompts/base-audit.md +22 -0
- code_mower/templates/lane_prompts/calibration-policy.md +21 -0
- code_mower/templates/lane_prompts/context-driven-quality.md +21 -0
- code_mower/templates/lane_prompts/docs-design.md +12 -0
- code_mower/templates/lane_prompts/generic-programming.md +21 -0
- code_mower/templates/lane_prompts/operability.md +22 -0
- code_mower/templates/lane_prompts/package-runtime.md +12 -0
- code_mower/templates/lane_prompts/security-threat-model.md +22 -0
- code_mower/templates/product-support/code_mower +216 -0
- code_mower/templates/product-support/code_mower_standalone_pin.env +7 -0
- code_mower/templates/product-support/code_mower_standalone_shadow.sh +151 -0
- code_mower/templates/product-support/run_claude_audit_pr.sh +32 -0
- code_mower/templates/product-support/run_codex_audit_pr.sh +32 -0
- code_mower/templates/product-support/safe_gh_comment.py +96 -0
- code_mower/templates/providers.yml +454 -0
- code_mower/templates/reviewer-spend.example.json +28 -0
- code_mower/templates/reviewer-value-report.example.md +20 -0
- code_mower/templates/workflows/private-standalone-shadow.yml.j2 +106 -0
- code_mower/templates/workflows/review-clear-stale.yml.j2 +83 -0
- code_mower/trailer_comment_labeler.py +207 -0
- code_mower/versioning.py +32 -0
- code_mower-0.5.0b5.dist-info/METADATA +302 -0
- code_mower-0.5.0b5.dist-info/RECORD +185 -0
- code_mower-0.5.0b5.dist-info/WHEEL +5 -0
- code_mower-0.5.0b5.dist-info/entry_points.txt +2 -0
- code_mower-0.5.0b5.dist-info/licenses/LICENSE +202 -0
- code_mower-0.5.0b5.dist-info/licenses/NOTICE +10 -0
- code_mower-0.5.0b5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"""Calibration reviewer result normalization."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Mapping
|
|
6
|
+
|
|
7
|
+
from .evidence import NON_BLOCKING_CODERABBIT_SEVERITIES
|
|
8
|
+
from .run_status import (
|
|
9
|
+
RUN_STATUS_AUDIT_INPUT_INSUFFICIENT,
|
|
10
|
+
RUN_STATUS_BLOCKED,
|
|
11
|
+
RUN_STATUS_INFRA_ERROR,
|
|
12
|
+
RUN_STATUS_PASS,
|
|
13
|
+
count_normalized_findings,
|
|
14
|
+
normalize_run_status_category,
|
|
15
|
+
status_from_verdict,
|
|
16
|
+
)
|
|
17
|
+
from .truth import expected_finding_matches, truth_for_item
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
AUDIT_INPUT_INSUFFICIENT_PATTERNS = (
|
|
21
|
+
"audit input incomplete",
|
|
22
|
+
"audit input is incomplete",
|
|
23
|
+
"audit input was incomplete",
|
|
24
|
+
"diff is incomplete",
|
|
25
|
+
"diff was incomplete",
|
|
26
|
+
"diff was truncated",
|
|
27
|
+
"diff truncation",
|
|
28
|
+
"incomplete diff",
|
|
29
|
+
"incomplete review context",
|
|
30
|
+
"insufficient audit input",
|
|
31
|
+
"review context is incomplete",
|
|
32
|
+
"truncated diff",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _finding_is_audit_input_insufficient(finding: Mapping[str, Any]) -> bool:
|
|
37
|
+
text = " ".join(
|
|
38
|
+
str(finding.get(key) or "").strip().lower()
|
|
39
|
+
for key in ("summary", "text", "message", "body", "title", "detail")
|
|
40
|
+
)
|
|
41
|
+
return any(pattern in text for pattern in AUDIT_INPUT_INSUFFICIENT_PATTERNS)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def audit_input_insufficient_result(findings: Any) -> bool:
|
|
45
|
+
if not isinstance(findings, list):
|
|
46
|
+
return False
|
|
47
|
+
blockers = [
|
|
48
|
+
finding
|
|
49
|
+
for finding in findings
|
|
50
|
+
if isinstance(finding, Mapping)
|
|
51
|
+
and str(finding.get("severity") or "").strip().upper() in {"P0", "P1", "P2"}
|
|
52
|
+
]
|
|
53
|
+
return bool(blockers) and all(
|
|
54
|
+
_finding_is_audit_input_insufficient(finding) for finding in blockers
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def coderabbit_blocking_findings(findings: Any) -> list[Mapping[str, Any]]:
|
|
59
|
+
if not isinstance(findings, list):
|
|
60
|
+
return []
|
|
61
|
+
blocking: list[Mapping[str, Any]] = []
|
|
62
|
+
for finding in findings:
|
|
63
|
+
if not isinstance(finding, Mapping):
|
|
64
|
+
continue
|
|
65
|
+
severity = str(finding.get("severity") or "").strip().lower()
|
|
66
|
+
if severity in NON_BLOCKING_CODERABBIT_SEVERITIES:
|
|
67
|
+
continue
|
|
68
|
+
blocking.append(finding)
|
|
69
|
+
return blocking
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def local_llm_findings(run: Mapping[str, Any]) -> list[dict[str, Any]]:
|
|
73
|
+
findings: list[dict[str, Any]] = []
|
|
74
|
+
for finding_group in run.get("blocker_findings", []) or []:
|
|
75
|
+
if not isinstance(finding_group, Mapping):
|
|
76
|
+
continue
|
|
77
|
+
path = str(finding_group.get("path") or "")
|
|
78
|
+
for text in finding_group.get("blockers", []) or []:
|
|
79
|
+
findings.append({"path": path, "text": str(text)})
|
|
80
|
+
for text in run.get("pr_level_blockers", []) or []:
|
|
81
|
+
findings.append({"path": "__pr__", "text": str(text)})
|
|
82
|
+
for finding_group in run.get("concern_findings", []) or []:
|
|
83
|
+
if not isinstance(finding_group, Mapping):
|
|
84
|
+
continue
|
|
85
|
+
path = str(finding_group.get("path") or "")
|
|
86
|
+
for text in finding_group.get("concerns", []) or []:
|
|
87
|
+
findings.append({"path": path, "text": str(text)})
|
|
88
|
+
return findings
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def observed_head_field(observed_head_sha: str, head_sha: str) -> dict[str, str]:
|
|
92
|
+
if observed_head_sha and observed_head_sha != head_sha:
|
|
93
|
+
return {"observed_head_sha": observed_head_sha}
|
|
94
|
+
return {}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def run_records_from_summary(
|
|
98
|
+
*,
|
|
99
|
+
summary: Mapping[str, Any],
|
|
100
|
+
item: Mapping[str, Any],
|
|
101
|
+
command_result: Mapping[str, Any],
|
|
102
|
+
) -> list[dict[str, Any]]:
|
|
103
|
+
artifact = str(command_result.get("summary_path") or "")
|
|
104
|
+
returncode = command_result.get("returncode")
|
|
105
|
+
try:
|
|
106
|
+
returncode_int = int(returncode) if returncode is not None else None
|
|
107
|
+
except (TypeError, ValueError):
|
|
108
|
+
returncode_int = None
|
|
109
|
+
mode = str(summary.get("mode") or "")
|
|
110
|
+
repo = str(summary.get("repo") or item.get("repo") or "")
|
|
111
|
+
pr_number = int(summary.get("pr_number") or item.get("pr_number") or 0)
|
|
112
|
+
head_sha = str(item.get("head_sha") or "")
|
|
113
|
+
observed_head_sha = str(summary.get("head_sha") or "")
|
|
114
|
+
truth = truth_for_item(item)
|
|
115
|
+
known_clean = bool(truth.get("known_clean"))
|
|
116
|
+
known_blocked = bool(truth.get("known_blocked"))
|
|
117
|
+
expected_findings = item.get("expected_findings", [])
|
|
118
|
+
calibration_run_id = str(command_result.get("run_id") or "")
|
|
119
|
+
replicate = command_result.get("replicate")
|
|
120
|
+
|
|
121
|
+
def expected_matches(status: str, finding_count: int, findings: Any = None) -> int:
|
|
122
|
+
if not known_blocked or status != RUN_STATUS_BLOCKED or finding_count <= 0:
|
|
123
|
+
return 0
|
|
124
|
+
if expected_findings:
|
|
125
|
+
return expected_finding_matches(expected_findings, findings)
|
|
126
|
+
return 1
|
|
127
|
+
|
|
128
|
+
if mode in {"antigravity-cli-audit", "gemini-cli-audit", "hermes-cli-audit"}:
|
|
129
|
+
default_reviewer = (
|
|
130
|
+
"antigravity-cli"
|
|
131
|
+
if mode == "antigravity-cli-audit"
|
|
132
|
+
else "hermes-cli"
|
|
133
|
+
if mode == "hermes-cli-audit"
|
|
134
|
+
else "gemini-cli"
|
|
135
|
+
)
|
|
136
|
+
reviewer_id = str(command_result.get("reviewer_id") or default_reviewer)
|
|
137
|
+
verdict = summary.get("verdict", {})
|
|
138
|
+
if not isinstance(verdict, Mapping):
|
|
139
|
+
verdict = {}
|
|
140
|
+
findings = verdict.get("findings", [])
|
|
141
|
+
parse_failed = bool(verdict.get("parse_failed"))
|
|
142
|
+
parse_status = "parse_failed" if parse_failed else "json"
|
|
143
|
+
result_category = normalize_run_status_category(verdict.get("result_category"))
|
|
144
|
+
audit_input_insufficient = (
|
|
145
|
+
result_category == RUN_STATUS_AUDIT_INPUT_INSUFFICIENT
|
|
146
|
+
or audit_input_insufficient_result(findings)
|
|
147
|
+
)
|
|
148
|
+
if parse_failed:
|
|
149
|
+
status = RUN_STATUS_INFRA_ERROR
|
|
150
|
+
elif audit_input_insufficient:
|
|
151
|
+
status = RUN_STATUS_AUDIT_INPUT_INSUFFICIENT
|
|
152
|
+
else:
|
|
153
|
+
status = status_from_verdict(verdict.get("verdict"), returncode=returncode_int)
|
|
154
|
+
finding_count = count_normalized_findings(findings)
|
|
155
|
+
return [
|
|
156
|
+
{
|
|
157
|
+
"reviewer": reviewer_id,
|
|
158
|
+
"status": status,
|
|
159
|
+
"finding_count": finding_count,
|
|
160
|
+
"expected_finding_matches": expected_matches(status, finding_count, findings),
|
|
161
|
+
"result_category": status if audit_input_insufficient else "",
|
|
162
|
+
"audit_input_insufficient_count": 1 if audit_input_insufficient else 0,
|
|
163
|
+
"known_clean": known_clean,
|
|
164
|
+
"known_blocked": known_blocked,
|
|
165
|
+
"duration_seconds": summary.get("duration_seconds"),
|
|
166
|
+
"parse_status": parse_status,
|
|
167
|
+
"artifact": artifact,
|
|
168
|
+
"model": summary.get("model") or "",
|
|
169
|
+
"repo": repo,
|
|
170
|
+
"pr_number": pr_number,
|
|
171
|
+
"head_sha": head_sha,
|
|
172
|
+
**observed_head_field(observed_head_sha, head_sha),
|
|
173
|
+
"calibration_run_id": calibration_run_id,
|
|
174
|
+
"replicate": replicate,
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
if mode == "coderabbit-cli-audit":
|
|
178
|
+
reviewer_id = str(command_result.get("reviewer_id") or "coderabbit-cli")
|
|
179
|
+
try:
|
|
180
|
+
raw_finding_count = int(summary.get("finding_count") or 0)
|
|
181
|
+
except (TypeError, ValueError) as exc:
|
|
182
|
+
raise ValueError("invalid coderabbit finding_count") from exc
|
|
183
|
+
findings = summary.get("findings", [])
|
|
184
|
+
if isinstance(findings, list) and findings:
|
|
185
|
+
parsed_finding_count = count_normalized_findings(findings)
|
|
186
|
+
blocking_findings = coderabbit_blocking_findings(findings)
|
|
187
|
+
if raw_finding_count > parsed_finding_count:
|
|
188
|
+
finding_count = raw_finding_count
|
|
189
|
+
else:
|
|
190
|
+
finding_count = len(blocking_findings)
|
|
191
|
+
else:
|
|
192
|
+
parsed_finding_count = 0
|
|
193
|
+
blocking_findings = []
|
|
194
|
+
finding_count = raw_finding_count
|
|
195
|
+
parse_status = str(summary.get("parse_status") or "").strip().lower()
|
|
196
|
+
head_check = summary.get("head_check", {})
|
|
197
|
+
head_check_status = (
|
|
198
|
+
str(head_check.get("status") or "")
|
|
199
|
+
if isinstance(head_check, Mapping)
|
|
200
|
+
else ""
|
|
201
|
+
)
|
|
202
|
+
if parse_status in {"raw", "parse_failed"}:
|
|
203
|
+
status = RUN_STATUS_INFRA_ERROR
|
|
204
|
+
elif head_check_status and head_check_status != "pass":
|
|
205
|
+
status = RUN_STATUS_INFRA_ERROR
|
|
206
|
+
elif returncode_int not in {None, 0} and not finding_count:
|
|
207
|
+
status = RUN_STATUS_INFRA_ERROR
|
|
208
|
+
else:
|
|
209
|
+
status = RUN_STATUS_BLOCKED if finding_count else RUN_STATUS_PASS
|
|
210
|
+
record = {
|
|
211
|
+
"reviewer": reviewer_id,
|
|
212
|
+
"status": status,
|
|
213
|
+
"finding_count": finding_count,
|
|
214
|
+
"expected_finding_matches": expected_matches(
|
|
215
|
+
status,
|
|
216
|
+
finding_count,
|
|
217
|
+
blocking_findings,
|
|
218
|
+
),
|
|
219
|
+
"known_clean": known_clean,
|
|
220
|
+
"known_blocked": known_blocked,
|
|
221
|
+
"duration_seconds": summary.get("duration_seconds"),
|
|
222
|
+
"parse_status": parse_status,
|
|
223
|
+
"artifact": artifact,
|
|
224
|
+
"repo": repo,
|
|
225
|
+
"pr_number": pr_number,
|
|
226
|
+
"head_sha": head_sha,
|
|
227
|
+
**observed_head_field(observed_head_sha, head_sha),
|
|
228
|
+
"calibration_run_id": calibration_run_id,
|
|
229
|
+
"replicate": replicate,
|
|
230
|
+
}
|
|
231
|
+
if raw_finding_count != finding_count:
|
|
232
|
+
record["raw_finding_count"] = raw_finding_count
|
|
233
|
+
if parsed_finding_count != raw_finding_count:
|
|
234
|
+
record["parsed_finding_count"] = parsed_finding_count
|
|
235
|
+
if raw_finding_count > parsed_finding_count:
|
|
236
|
+
record["unparsed_finding_count"] = raw_finding_count - parsed_finding_count
|
|
237
|
+
non_blocking_finding_count = parsed_finding_count - len(blocking_findings)
|
|
238
|
+
if non_blocking_finding_count:
|
|
239
|
+
record["non_blocking_finding_count"] = non_blocking_finding_count
|
|
240
|
+
return [record]
|
|
241
|
+
if mode == "local-llm-bakeoff":
|
|
242
|
+
records: list[dict[str, Any]] = []
|
|
243
|
+
for run in summary.get("runs", []) or []:
|
|
244
|
+
if not isinstance(run, Mapping):
|
|
245
|
+
continue
|
|
246
|
+
finding_count = (
|
|
247
|
+
int(run.get("blocker_file_count") or 0)
|
|
248
|
+
+ int(run.get("concern_file_count") or 0)
|
|
249
|
+
+ len(run.get("pr_level_blockers", []) or [])
|
|
250
|
+
)
|
|
251
|
+
findings = local_llm_findings(run)
|
|
252
|
+
status = status_from_verdict(run.get("verdict"), returncode=returncode_int)
|
|
253
|
+
records.append(
|
|
254
|
+
{
|
|
255
|
+
"reviewer": str(run.get("profile_id") or "local-llm"),
|
|
256
|
+
"status": status,
|
|
257
|
+
"finding_count": finding_count,
|
|
258
|
+
"expected_finding_matches": expected_matches(
|
|
259
|
+
status,
|
|
260
|
+
finding_count,
|
|
261
|
+
findings,
|
|
262
|
+
),
|
|
263
|
+
"known_clean": known_clean,
|
|
264
|
+
"known_blocked": known_blocked,
|
|
265
|
+
"duration_seconds": run.get("duration_seconds"),
|
|
266
|
+
"parse_status": (
|
|
267
|
+
"parse_failed"
|
|
268
|
+
if int(run.get("parse_failure_count") or 0)
|
|
269
|
+
else "json"
|
|
270
|
+
),
|
|
271
|
+
"artifact": artifact,
|
|
272
|
+
"model": str(run.get("model") or ""),
|
|
273
|
+
"repo": str(run.get("repo") or repo),
|
|
274
|
+
"pr_number": int(run.get("pr_number") or pr_number),
|
|
275
|
+
"head_sha": head_sha,
|
|
276
|
+
**observed_head_field(
|
|
277
|
+
str(run.get("head_sha_end") or observed_head_sha),
|
|
278
|
+
head_sha,
|
|
279
|
+
),
|
|
280
|
+
"calibration_run_id": calibration_run_id,
|
|
281
|
+
"replicate": replicate,
|
|
282
|
+
}
|
|
283
|
+
)
|
|
284
|
+
return records
|
|
285
|
+
return []
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def infra_run_record(
|
|
289
|
+
*,
|
|
290
|
+
lane_id: str,
|
|
291
|
+
item: Mapping[str, Any],
|
|
292
|
+
status: str,
|
|
293
|
+
duration_seconds: float,
|
|
294
|
+
artifact: str,
|
|
295
|
+
) -> dict[str, Any]:
|
|
296
|
+
reviewer = lane_id
|
|
297
|
+
if reviewer == "local-llm":
|
|
298
|
+
reviewer = "local-llm"
|
|
299
|
+
truth = truth_for_item(item)
|
|
300
|
+
return {
|
|
301
|
+
"reviewer": reviewer,
|
|
302
|
+
"status": status,
|
|
303
|
+
"finding_count": 0,
|
|
304
|
+
"expected_finding_matches": 0,
|
|
305
|
+
"known_clean": bool(truth.get("known_clean")),
|
|
306
|
+
"known_blocked": bool(truth.get("known_blocked")),
|
|
307
|
+
"duration_seconds": round(duration_seconds, 3),
|
|
308
|
+
"parse_status": status,
|
|
309
|
+
"artifact": artifact,
|
|
310
|
+
"repo": str(item.get("repo") or ""),
|
|
311
|
+
"pr_number": int(item.get("pr_number") or 0),
|
|
312
|
+
"head_sha": str(item.get("head_sha") or ""),
|
|
313
|
+
"calibration_run_id": str(item.get("calibration_run_id") or ""),
|
|
314
|
+
"replicate": item.get("replicate"),
|
|
315
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Calibration run-results ingestion and corpus folding."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Iterable, Mapping, Sequence
|
|
8
|
+
|
|
9
|
+
from .corpus import load_json_object
|
|
10
|
+
from .evidence import KNOWN_EVIDENCE_DISPOSITIONS
|
|
11
|
+
from .run_status import normalize_run_status_category
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
CALIBRATION_RUN_RESULTS_MODE = "code-mower-calibration-run-results"
|
|
15
|
+
CALIBRATION_RUN_RESULTS_SCHEMA = "code_mower.calibrationRunResults.v1"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_run_results(paths: Iterable[Path]) -> list[Mapping[str, Any]]:
|
|
19
|
+
reports: list[Mapping[str, Any]] = []
|
|
20
|
+
for path in paths:
|
|
21
|
+
payload = load_json_object(path)
|
|
22
|
+
if payload.get("mode") != CALIBRATION_RUN_RESULTS_MODE:
|
|
23
|
+
raise ValueError(f"{path} is not a Code Mower calibration run-results file")
|
|
24
|
+
reports.append(payload)
|
|
25
|
+
return reports
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def csv_values(value: Any) -> set[str]:
|
|
29
|
+
if isinstance(value, str):
|
|
30
|
+
return {part.strip() for part in value.split(",") if part.strip()}
|
|
31
|
+
if isinstance(value, Sequence) and not isinstance(value, (bytes, bytearray)):
|
|
32
|
+
return {str(part).strip() for part in value if str(part).strip()}
|
|
33
|
+
if value is None:
|
|
34
|
+
return set()
|
|
35
|
+
return {str(value).strip()} if str(value).strip() else set()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def normalize_disposition(value: Any) -> str:
|
|
39
|
+
disposition = str(value or "unknown").strip().lower().replace("-", "_")
|
|
40
|
+
if disposition not in KNOWN_EVIDENCE_DISPOSITIONS:
|
|
41
|
+
return "unknown"
|
|
42
|
+
return disposition
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def run_matches_disposition_rule(
|
|
46
|
+
run: Mapping[str, Any],
|
|
47
|
+
rule: Mapping[str, Any],
|
|
48
|
+
) -> bool:
|
|
49
|
+
reviewers = csv_values(rule.get("reviewer") or rule.get("profile_id") or rule.get("lane"))
|
|
50
|
+
if reviewers:
|
|
51
|
+
reviewer = str(
|
|
52
|
+
run.get("reviewer")
|
|
53
|
+
or run.get("profile_id")
|
|
54
|
+
or run.get("lane")
|
|
55
|
+
or "unknown-reviewer"
|
|
56
|
+
).strip()
|
|
57
|
+
if reviewer not in reviewers:
|
|
58
|
+
return False
|
|
59
|
+
statuses = {
|
|
60
|
+
normalize_run_status_category(status)
|
|
61
|
+
for status in csv_values(rule.get("status") or rule.get("status_category"))
|
|
62
|
+
}
|
|
63
|
+
if statuses:
|
|
64
|
+
run_status = normalize_run_status_category(run.get("status") or run.get("verdict"))
|
|
65
|
+
if run_status not in statuses:
|
|
66
|
+
return False
|
|
67
|
+
result_categories = csv_values(rule.get("result_category"))
|
|
68
|
+
if result_categories:
|
|
69
|
+
if str(run.get("result_category") or "").strip() not in result_categories:
|
|
70
|
+
return False
|
|
71
|
+
try:
|
|
72
|
+
min_findings = int(rule.get("min_finding_count") or 0)
|
|
73
|
+
except (TypeError, ValueError):
|
|
74
|
+
min_findings = 0
|
|
75
|
+
if min_findings:
|
|
76
|
+
try:
|
|
77
|
+
finding_count = int(run.get("finding_count") or 0)
|
|
78
|
+
except (TypeError, ValueError):
|
|
79
|
+
finding_count = 0
|
|
80
|
+
if finding_count < min_findings:
|
|
81
|
+
return False
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def apply_run_disposition_rules(run: dict[str, Any], item: Mapping[str, Any]) -> None:
|
|
86
|
+
for rule in item.get("reviewer_run_dispositions", []) or []:
|
|
87
|
+
if not isinstance(rule, Mapping):
|
|
88
|
+
continue
|
|
89
|
+
if not run_matches_disposition_rule(run, rule):
|
|
90
|
+
continue
|
|
91
|
+
disposition = normalize_disposition(rule.get("disposition"))
|
|
92
|
+
if disposition != "unknown" and not run.get("disposition"):
|
|
93
|
+
run["disposition"] = disposition
|
|
94
|
+
if rule.get("expected_blocker_caught") is not None:
|
|
95
|
+
run["expected_blocker_caught"] = bool(rule.get("expected_blocker_caught"))
|
|
96
|
+
if rule.get("notes") and not run.get("disposition_notes"):
|
|
97
|
+
run["disposition_notes"] = str(rule.get("notes") or "")
|
|
98
|
+
# Apply the first matching adjudication rule. Additional notes can be
|
|
99
|
+
# modeled as reviewer_evidence if a corpus needs more detail.
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def corpus_with_run_results(
|
|
104
|
+
corpus: Mapping[str, Any],
|
|
105
|
+
run_results: Iterable[Mapping[str, Any]],
|
|
106
|
+
) -> dict[str, Any]:
|
|
107
|
+
merged = copy.deepcopy(dict(corpus))
|
|
108
|
+
items = merged.get("corpus", [])
|
|
109
|
+
if not isinstance(items, list):
|
|
110
|
+
raise ValueError("calibration corpus must include a corpus list")
|
|
111
|
+
item_by_key: dict[tuple[str, int, str], dict[str, Any]] = {}
|
|
112
|
+
for item in items:
|
|
113
|
+
if not isinstance(item, dict):
|
|
114
|
+
continue
|
|
115
|
+
key = (
|
|
116
|
+
str(item.get("repo") or ""),
|
|
117
|
+
int(item.get("pr_number") or 0),
|
|
118
|
+
str(item.get("head_sha") or ""),
|
|
119
|
+
)
|
|
120
|
+
item_by_key[key] = item
|
|
121
|
+
|
|
122
|
+
for result_index, result_report in enumerate(run_results):
|
|
123
|
+
manifest_id = str(
|
|
124
|
+
result_report.get("run_results_id")
|
|
125
|
+
or result_report.get("started_at")
|
|
126
|
+
or result_index
|
|
127
|
+
)
|
|
128
|
+
for run in result_report.get("reviewer_runs", []) or []:
|
|
129
|
+
if not isinstance(run, Mapping):
|
|
130
|
+
continue
|
|
131
|
+
key = (
|
|
132
|
+
str(run.get("repo") or ""),
|
|
133
|
+
int(run.get("pr_number") or 0),
|
|
134
|
+
str(run.get("head_sha") or ""),
|
|
135
|
+
)
|
|
136
|
+
item = item_by_key.get(key)
|
|
137
|
+
if item is None:
|
|
138
|
+
continue
|
|
139
|
+
folded_run = {
|
|
140
|
+
key: value
|
|
141
|
+
for key, value in run.items()
|
|
142
|
+
if key not in {"repo", "pr_number", "head_sha"}
|
|
143
|
+
}
|
|
144
|
+
folded_run.setdefault("calibration_manifest_id", manifest_id)
|
|
145
|
+
apply_run_disposition_rules(folded_run, item)
|
|
146
|
+
item.setdefault("reviewer_runs", []).append(folded_run)
|
|
147
|
+
return merged
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Reviewer run status normalization for calibration reports."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Mapping
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
RUN_STATUS_PASS = "pass"
|
|
9
|
+
RUN_STATUS_BLOCKED = "blocked"
|
|
10
|
+
RUN_STATUS_AUDIT_INPUT_INSUFFICIENT = "audit_input_insufficient"
|
|
11
|
+
RUN_STATUS_INFRA_ERROR = "infra_error"
|
|
12
|
+
RUN_STATUS_UNKNOWN = "unknown"
|
|
13
|
+
|
|
14
|
+
RUN_STATUS_CATEGORY_ALIASES = {
|
|
15
|
+
"pass": RUN_STATUS_PASS,
|
|
16
|
+
"passed": RUN_STATUS_PASS,
|
|
17
|
+
"complete": RUN_STATUS_PASS,
|
|
18
|
+
"completed": RUN_STATUS_PASS,
|
|
19
|
+
"done": RUN_STATUS_PASS,
|
|
20
|
+
"success": RUN_STATUS_PASS,
|
|
21
|
+
"succeeded": RUN_STATUS_PASS,
|
|
22
|
+
"block": RUN_STATUS_BLOCKED,
|
|
23
|
+
"blocked": RUN_STATUS_BLOCKED,
|
|
24
|
+
"audit_input_insufficient": RUN_STATUS_AUDIT_INPUT_INSUFFICIENT,
|
|
25
|
+
"context_insufficient": RUN_STATUS_AUDIT_INPUT_INSUFFICIENT,
|
|
26
|
+
"fail": RUN_STATUS_BLOCKED,
|
|
27
|
+
"error": RUN_STATUS_INFRA_ERROR,
|
|
28
|
+
"failed": RUN_STATUS_INFRA_ERROR,
|
|
29
|
+
"failure": RUN_STATUS_INFRA_ERROR,
|
|
30
|
+
"infra_error": RUN_STATUS_INFRA_ERROR,
|
|
31
|
+
"input_insufficient": RUN_STATUS_AUDIT_INPUT_INSUFFICIENT,
|
|
32
|
+
"insufficient_context": RUN_STATUS_AUDIT_INPUT_INSUFFICIENT,
|
|
33
|
+
"invalid_summary": RUN_STATUS_INFRA_ERROR,
|
|
34
|
+
"launch_failed": RUN_STATUS_INFRA_ERROR,
|
|
35
|
+
"missing_summary": RUN_STATUS_INFRA_ERROR,
|
|
36
|
+
"rate_limit": RUN_STATUS_INFRA_ERROR,
|
|
37
|
+
"rate_limited": RUN_STATUS_INFRA_ERROR,
|
|
38
|
+
"setup_error": RUN_STATUS_INFRA_ERROR,
|
|
39
|
+
"stale": RUN_STATUS_INFRA_ERROR,
|
|
40
|
+
"timeout": RUN_STATUS_INFRA_ERROR,
|
|
41
|
+
"timed_out": RUN_STATUS_INFRA_ERROR,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def normalize_run_status_category(value: Any) -> str:
|
|
46
|
+
"""Return the semantic category used for reviewer-run policy decisions."""
|
|
47
|
+
|
|
48
|
+
status = str(value or "").strip().lower().replace("-", "_")
|
|
49
|
+
return RUN_STATUS_CATEGORY_ALIASES.get(status, RUN_STATUS_UNKNOWN)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def status_from_verdict(value: Any, *, returncode: int | None = None) -> str:
|
|
53
|
+
category = normalize_run_status_category(value)
|
|
54
|
+
if category != RUN_STATUS_UNKNOWN:
|
|
55
|
+
return category
|
|
56
|
+
if returncode is not None and returncode != 0:
|
|
57
|
+
return RUN_STATUS_INFRA_ERROR
|
|
58
|
+
return RUN_STATUS_UNKNOWN
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def count_normalized_findings(findings: Any) -> int:
|
|
62
|
+
if not isinstance(findings, list):
|
|
63
|
+
return 0
|
|
64
|
+
return sum(1 for finding in findings if isinstance(finding, Mapping))
|