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.
Files changed (185) hide show
  1. code_mower/__init__.py +3 -0
  2. code_mower/adapters/__init__.py +39 -0
  3. code_mower/adapters/_base.py +148 -0
  4. code_mower/adapters/cursor_bugbot.py +97 -0
  5. code_mower/adapters/gitar.py +111 -0
  6. code_mower/adapters/greptile.py +191 -0
  7. code_mower/adapters/qodo.py +140 -0
  8. code_mower/antigravity_cli_audit_pr.py +241 -0
  9. code_mower/audit_handoff_log.py +345 -0
  10. code_mower/audit_labeler_lib.py +311 -0
  11. code_mower/audit_progress.py +203 -0
  12. code_mower/blind_review_artifacts.py +562 -0
  13. code_mower/blind_review_coordinator.py +276 -0
  14. code_mower/bootstrap.py +524 -0
  15. code_mower/builder_experiment.py +539 -0
  16. code_mower/calibration/__init__.py +181 -0
  17. code_mower/calibration/arms.py +267 -0
  18. code_mower/calibration/auto_discovery.py +348 -0
  19. code_mower/calibration/commands.py +192 -0
  20. code_mower/calibration/context_inputs.py +199 -0
  21. code_mower/calibration/corpus.py +91 -0
  22. code_mower/calibration/evidence.py +20 -0
  23. code_mower/calibration/evidence_report.py +360 -0
  24. code_mower/calibration/identity.py +27 -0
  25. code_mower/calibration/metrics.py +18 -0
  26. code_mower/calibration/overlap.py +103 -0
  27. code_mower/calibration/planning.py +309 -0
  28. code_mower/calibration/policy.py +207 -0
  29. code_mower/calibration/results.py +315 -0
  30. code_mower/calibration/run_results.py +147 -0
  31. code_mower/calibration/run_status.py +64 -0
  32. code_mower/calibration/runner.py +360 -0
  33. code_mower/calibration/truth.py +188 -0
  34. code_mower/calibration/value_report.py +142 -0
  35. code_mower/checks.py +402 -0
  36. code_mower/claude_audit_pr.py +1126 -0
  37. code_mower/claude_cli_bounce.py +303 -0
  38. code_mower/claude_cli_environment.py +73 -0
  39. code_mower/clear_stale.py +374 -0
  40. code_mower/cli.py +537 -0
  41. code_mower/cloud.py +674 -0
  42. code_mower/cloud_client/__init__.py +173 -0
  43. code_mower/cloud_client/bundle.py +155 -0
  44. code_mower/cloud_client/doctor.py +206 -0
  45. code_mower/cloud_client/dogfood.py +78 -0
  46. code_mower/cloud_client/endpoints.py +114 -0
  47. code_mower/cloud_client/errors.py +7 -0
  48. code_mower/cloud_client/events.py +278 -0
  49. code_mower/cloud_client/export.py +272 -0
  50. code_mower/cloud_client/git_metadata.py +46 -0
  51. code_mower/cloud_client/manifest.py +39 -0
  52. code_mower/cloud_client/operations.py +448 -0
  53. code_mower/cloud_client/reports.py +45 -0
  54. code_mower/cloud_client/setup.py +205 -0
  55. code_mower/cloud_client/upload.py +97 -0
  56. code_mower/code_mower_calibration.py +598 -0
  57. code_mower/code_mower_context_packs.py +591 -0
  58. code_mower/code_mower_merge.py +227 -0
  59. code_mower/code_mower_telemetry.py +561 -0
  60. code_mower/coderabbit_cli_audit_pr.py +526 -0
  61. code_mower/codex_audit_env_preflight.py +220 -0
  62. code_mower/codex_audit_pr.py +1738 -0
  63. code_mower/codex_audit_schema_smoke.py +160 -0
  64. code_mower/codex_audit_verdict.schema.json +44 -0
  65. code_mower/config.py +655 -0
  66. code_mower/doctor.py +161 -0
  67. code_mower/doctor_checks/__init__.py +104 -0
  68. code_mower/doctor_checks/cloud.py +129 -0
  69. code_mower/doctor_checks/common.py +215 -0
  70. code_mower/doctor_checks/github.py +128 -0
  71. code_mower/doctor_checks/github_actions.py +11 -0
  72. code_mower/doctor_checks/github_actions_cost.py +99 -0
  73. code_mower/doctor_checks/github_actions_cost_summary.py +111 -0
  74. code_mower/doctor_checks/github_actions_failure_annotations.py +27 -0
  75. code_mower/doctor_checks/github_actions_failure_models.py +47 -0
  76. code_mower/doctor_checks/github_actions_failure_scan.py +200 -0
  77. code_mower/doctor_checks/github_actions_failure_selection.py +63 -0
  78. code_mower/doctor_checks/github_actions_failures.py +103 -0
  79. code_mower/doctor_checks/github_actions_permissions.py +55 -0
  80. code_mower/doctor_checks/github_api.py +79 -0
  81. code_mower/doctor_checks/github_branch.py +56 -0
  82. code_mower/doctor_checks/github_config.py +25 -0
  83. code_mower/doctor_checks/github_provider.py +61 -0
  84. code_mower/doctor_checks/github_repo.py +120 -0
  85. code_mower/doctor_checks/groups.py +36 -0
  86. code_mower/doctor_checks/models.py +96 -0
  87. code_mower/doctor_checks/output.py +86 -0
  88. code_mower/doctor_checks/presets.py +64 -0
  89. code_mower/doctor_checks/privacy.py +20 -0
  90. code_mower/doctor_checks/provider_api_model.py +138 -0
  91. code_mower/doctor_checks/provider_api_model_openai.py +29 -0
  92. code_mower/doctor_checks/provider_api_model_profiles.py +137 -0
  93. code_mower/doctor_checks/provider_env.py +113 -0
  94. code_mower/doctor_checks/provider_env_required.py +56 -0
  95. code_mower/doctor_checks/provider_env_tokens.py +100 -0
  96. code_mower/doctor_checks/provider_local_cli.py +162 -0
  97. code_mower/doctor_checks/provider_local_cli_commands.py +47 -0
  98. code_mower/doctor_checks/provider_local_cli_probe_config.py +70 -0
  99. code_mower/doctor_checks/provider_probe.py +20 -0
  100. code_mower/doctor_checks/provider_probe_auth.py +52 -0
  101. code_mower/doctor_checks/provider_probe_evaluation.py +109 -0
  102. code_mower/doctor_checks/provider_probe_json.py +45 -0
  103. code_mower/doctor_checks/provider_probe_remediation.py +39 -0
  104. code_mower/doctor_checks/providers.py +159 -0
  105. code_mower/doctor_checks/registry.py +69 -0
  106. code_mower/doctor_checks/runner.py +188 -0
  107. code_mower/doctor_checks/runtime.py +89 -0
  108. code_mower/doctor_checks/runtime_github_auth.py +148 -0
  109. code_mower/gemini_cli_audit_pr.py +897 -0
  110. code_mower/hermes_cli_audit_pr.py +436 -0
  111. code_mower/init.py +888 -0
  112. code_mower/lane_configs/__init__.py +37 -0
  113. code_mower/lane_configs/aider.py +32 -0
  114. code_mower/lane_configs/antigravity_cli.py +35 -0
  115. code_mower/lane_configs/claude.py +35 -0
  116. code_mower/lane_configs/codex.py +32 -0
  117. code_mower/lane_configs/devin.py +33 -0
  118. code_mower/lane_configs/gemini_cli.py +35 -0
  119. code_mower/lane_configs/hermes_cli.py +35 -0
  120. code_mower/lane_configs/local_llm.py +31 -0
  121. code_mower/local_llm_audit_pr.py +1364 -0
  122. code_mower/local_llm_bakeoff.py +458 -0
  123. code_mower/local_llm_calibration.py +441 -0
  124. code_mower/local_llm_profiles.py +66 -0
  125. code_mower/migration.py +508 -0
  126. code_mower/migration_install.py +292 -0
  127. code_mower/migration_mirror.py +392 -0
  128. code_mower/migration_readiness.py +237 -0
  129. code_mower/migration_rehearsal.py +718 -0
  130. code_mower/next_steps.py +441 -0
  131. code_mower/package.py +673 -0
  132. code_mower/package_content.py +444 -0
  133. code_mower/package_manifest.py +452 -0
  134. code_mower/package_paths.py +53 -0
  135. code_mower/package_rendering.py +90 -0
  136. code_mower/package_static.py +585 -0
  137. code_mower/prompts.py +267 -0
  138. code_mower/provider_registry.py +469 -0
  139. code_mower/provider_runners/__init__.py +60 -0
  140. code_mower/provider_runners/comments.py +31 -0
  141. code_mower/provider_runners/git.py +46 -0
  142. code_mower/provider_runners/github_auth.py +61 -0
  143. code_mower/provider_runners/github_pr.py +120 -0
  144. code_mower/provider_runners/process.py +58 -0
  145. code_mower/provider_runners/repo_paths.py +23 -0
  146. code_mower/provider_runners/text_schema.py +41 -0
  147. code_mower/provider_runners/verdict_artifacts.py +103 -0
  148. code_mower/provider_runners/workspace.py +57 -0
  149. code_mower/release_readiness.py +549 -0
  150. code_mower/reviewer_metrics.py +389 -0
  151. code_mower/saas_reviewer_labeler.py +809 -0
  152. code_mower/secrets.py +89 -0
  153. code_mower/templates/builder-experiment.example.json +55 -0
  154. code_mower/templates/calibration-corpus.example.json +129 -0
  155. code_mower/templates/calibration-corpus.json +129 -0
  156. code_mower/templates/code-mower.example.yml +423 -0
  157. code_mower/templates/context-packs.example.json +150 -0
  158. code_mower/templates/lane_prompts/base-audit.md +22 -0
  159. code_mower/templates/lane_prompts/calibration-policy.md +21 -0
  160. code_mower/templates/lane_prompts/context-driven-quality.md +21 -0
  161. code_mower/templates/lane_prompts/docs-design.md +12 -0
  162. code_mower/templates/lane_prompts/generic-programming.md +21 -0
  163. code_mower/templates/lane_prompts/operability.md +22 -0
  164. code_mower/templates/lane_prompts/package-runtime.md +12 -0
  165. code_mower/templates/lane_prompts/security-threat-model.md +22 -0
  166. code_mower/templates/product-support/code_mower +216 -0
  167. code_mower/templates/product-support/code_mower_standalone_pin.env +7 -0
  168. code_mower/templates/product-support/code_mower_standalone_shadow.sh +151 -0
  169. code_mower/templates/product-support/run_claude_audit_pr.sh +32 -0
  170. code_mower/templates/product-support/run_codex_audit_pr.sh +32 -0
  171. code_mower/templates/product-support/safe_gh_comment.py +96 -0
  172. code_mower/templates/providers.yml +454 -0
  173. code_mower/templates/reviewer-spend.example.json +28 -0
  174. code_mower/templates/reviewer-value-report.example.md +20 -0
  175. code_mower/templates/workflows/private-standalone-shadow.yml.j2 +106 -0
  176. code_mower/templates/workflows/review-clear-stale.yml.j2 +83 -0
  177. code_mower/trailer_comment_labeler.py +207 -0
  178. code_mower/versioning.py +32 -0
  179. code_mower-0.5.0b5.dist-info/METADATA +302 -0
  180. code_mower-0.5.0b5.dist-info/RECORD +185 -0
  181. code_mower-0.5.0b5.dist-info/WHEEL +5 -0
  182. code_mower-0.5.0b5.dist-info/entry_points.txt +2 -0
  183. code_mower-0.5.0b5.dist-info/licenses/LICENSE +202 -0
  184. code_mower-0.5.0b5.dist-info/licenses/NOTICE +10 -0
  185. code_mower-0.5.0b5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,276 @@
1
+ #!/usr/bin/env python3
2
+ """Plan blind-review hold/release state from provider-neutral lane events."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import sys
9
+ from datetime import datetime, timezone
10
+ from pathlib import Path
11
+ from typing import Any, Mapping
12
+
13
+
14
+ DONE_STATES = {"done", "pass", "passed", "completed", "success"}
15
+ BLOCKED_STATES = {"blocked", "failed", "failure", "error"}
16
+ PENDING_STATES = {"needs", "pending", "queued", "running", "requested", "stale"}
17
+ EVENT_TIMESTAMP_FIELDS = ("timestamp", "updated_at", "submitted_at", "created_at")
18
+
19
+
20
+ def _strip_prefix(value: str, prefix: str) -> str:
21
+ if value.startswith(prefix):
22
+ return value[len(prefix):]
23
+ return value
24
+
25
+
26
+ def normalize_state(value: Any) -> str:
27
+ text = str(value or "pending").strip().lower().replace("_", "-")
28
+ text = _strip_prefix(_strip_prefix(text, "audit-"), "review-")
29
+ if text in DONE_STATES:
30
+ return "done"
31
+ if text in BLOCKED_STATES:
32
+ return "blocked"
33
+ if text in PENDING_STATES:
34
+ return "pending"
35
+ return "pending"
36
+
37
+
38
+ def _load_manifest(path: Path) -> Mapping[str, Any]:
39
+ try:
40
+ payload = json.loads(path.read_text(encoding="utf-8"))
41
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
42
+ raise ValueError(f"unable to read {path}: {exc}") from exc
43
+ if not isinstance(payload, Mapping):
44
+ raise ValueError("blind-review manifest must be a JSON object")
45
+ return payload
46
+
47
+
48
+ def _required_lanes(manifest: Mapping[str, Any]) -> list[str]:
49
+ raw_lanes = manifest.get("required_lanes", [])
50
+ if raw_lanes is None:
51
+ raw_lanes = []
52
+ if not isinstance(raw_lanes, list):
53
+ raise ValueError("required_lanes must be a JSON array of lane IDs")
54
+
55
+ lanes: list[str] = []
56
+ for index, lane in enumerate(raw_lanes):
57
+ if not isinstance(lane, str) or not lane.strip():
58
+ raise ValueError(
59
+ f"required_lanes[{index}] must be a non-empty string lane ID"
60
+ )
61
+ lanes.append(lane.strip())
62
+ return lanes
63
+
64
+
65
+ def _event_timestamp(event: Mapping[str, Any]) -> str:
66
+ for field in EVENT_TIMESTAMP_FIELDS:
67
+ value = event.get(field)
68
+ if isinstance(value, str) and value.strip():
69
+ return value.strip()
70
+ return ""
71
+
72
+
73
+ def _parse_event_timestamp(event: Mapping[str, Any]) -> datetime | None:
74
+ timestamp = _event_timestamp(event)
75
+ if not timestamp:
76
+ return None
77
+ normalized = timestamp
78
+ if normalized.endswith("Z"):
79
+ normalized = normalized[:-1] + "+00:00"
80
+ try:
81
+ parsed = datetime.fromisoformat(normalized)
82
+ except ValueError:
83
+ return None
84
+ if parsed.tzinfo is None:
85
+ parsed = parsed.replace(tzinfo=timezone.utc)
86
+ return parsed.astimezone(timezone.utc)
87
+
88
+
89
+ def _latest_events(events: Any) -> dict[str, Mapping[str, Any]]:
90
+ latest: dict[str, Mapping[str, Any]] = {}
91
+ lane_events: dict[str, list[Mapping[str, Any]]] = {}
92
+ if not isinstance(events, list):
93
+ return latest
94
+ for event in events:
95
+ if not isinstance(event, Mapping):
96
+ continue
97
+ lane = str(event.get("lane") or "")
98
+ if not lane:
99
+ continue
100
+ lane_events.setdefault(lane, []).append(event)
101
+ for lane, items in lane_events.items():
102
+ timestamps = [_parse_event_timestamp(event) for event in items]
103
+ if all(timestamp is not None for timestamp in timestamps):
104
+ latest_index = max(
105
+ range(len(items)),
106
+ key=lambda index: (timestamps[index], index),
107
+ )
108
+ latest[lane] = items[latest_index]
109
+ else:
110
+ latest[lane] = items[-1]
111
+ return latest
112
+
113
+
114
+ def build_blind_review_plan(manifest: Mapping[str, Any]) -> dict[str, Any]:
115
+ required_lanes = _required_lanes(manifest)
116
+ latest = _latest_events(manifest.get("events", []))
117
+ expected_head = str(manifest.get("head_sha") or "")
118
+ lane_states: dict[str, dict[str, Any]] = {}
119
+ warnings: list[str] = []
120
+
121
+ for lane in required_lanes:
122
+ event = latest.get(lane, {})
123
+ raw_state = event.get("state", "pending")
124
+ state = normalize_state(raw_state)
125
+ event_head = str(event.get("head_sha") or "")
126
+ if expected_head and not event_head:
127
+ warnings.append(
128
+ f"{lane}: event head is missing while expected head is {expected_head[:12]}; holding release."
129
+ )
130
+ state = "pending"
131
+ elif expected_head and event_head != expected_head:
132
+ warnings.append(
133
+ f"{lane}: event head {event_head[:12]} does not match expected head {expected_head[:12]}; holding release."
134
+ )
135
+ state = "pending"
136
+ lane_states[lane] = {
137
+ "state": state,
138
+ "raw_state": str(raw_state),
139
+ "artifact": str(event.get("artifact") or event.get("url") or ""),
140
+ "head_sha": event_head,
141
+ "detail": str(event.get("detail") or ""),
142
+ }
143
+
144
+ pending_lanes = [
145
+ lane for lane, state in lane_states.items()
146
+ if state["state"] == "pending"
147
+ ]
148
+ blocked_lanes = [
149
+ lane for lane, state in lane_states.items()
150
+ if state["state"] == "blocked"
151
+ ]
152
+ done_lanes = [
153
+ lane for lane, state in lane_states.items()
154
+ if state["state"] == "done"
155
+ ]
156
+ ready_to_release = bool(required_lanes) and not pending_lanes and not blocked_lanes
157
+ release_order = done_lanes if ready_to_release else []
158
+ if not required_lanes:
159
+ warnings.append("no required_lanes configured; blind review cannot release")
160
+
161
+ return {
162
+ "mode": "blind-review-plan",
163
+ "repo": manifest.get("repo", ""),
164
+ "pr_number": manifest.get("pr_number", ""),
165
+ "head_sha": expected_head,
166
+ "required_lanes": required_lanes,
167
+ "lane_states": lane_states,
168
+ "done_lanes": done_lanes,
169
+ "pending_lanes": pending_lanes,
170
+ "blocked_lanes": blocked_lanes,
171
+ "ready_to_release": ready_to_release,
172
+ "release_order": release_order,
173
+ "public_mode": (
174
+ "release_all_artifacts"
175
+ if ready_to_release
176
+ else "hold_artifacts"
177
+ ),
178
+ "warnings": warnings,
179
+ }
180
+
181
+
182
+ def render_blind_review_text(plan: Mapping[str, Any]) -> str:
183
+ repo = plan.get("repo") or "unknown repo"
184
+ pr_number = plan.get("pr_number") or "?"
185
+ lines = [
186
+ f"Blind review plan for {repo}#{pr_number}",
187
+ f"ready_to_release: {str(plan.get('ready_to_release')).lower()}",
188
+ "",
189
+ "Lane states:",
190
+ ]
191
+ lane_states = plan.get("lane_states", {})
192
+ if isinstance(lane_states, Mapping):
193
+ for lane, state in lane_states.items():
194
+ if not isinstance(state, Mapping):
195
+ continue
196
+ artifact = f" artifact={state.get('artifact')}" if state.get("artifact") else ""
197
+ lines.append(f"- {lane}: {state.get('state')}{artifact}")
198
+ if plan.get("pending_lanes"):
199
+ lines.extend(["", "Pending lanes:"])
200
+ lines.extend(f"- {lane}" for lane in plan.get("pending_lanes", []))
201
+ if plan.get("blocked_lanes"):
202
+ lines.extend(["", "Blocked lanes:"])
203
+ lines.extend(f"- {lane}" for lane in plan.get("blocked_lanes", []))
204
+ if plan.get("warnings"):
205
+ lines.extend(["", "Warnings:"])
206
+ lines.extend(f"- {warning}" for warning in plan.get("warnings", []))
207
+ return "\n".join(lines) + "\n"
208
+
209
+
210
+ def main(argv: list[str] | None = None) -> int:
211
+ parser = argparse.ArgumentParser(description=__doc__)
212
+ subparsers = parser.add_subparsers(dest="command", required=True)
213
+ artifacts_parser = subparsers.add_parser("artifacts")
214
+ artifacts_parser.add_argument("manifest", type=Path)
215
+ artifacts_parser.add_argument("--output-dir", default=".code-mower/blind-review")
216
+ artifacts_parser.add_argument(
217
+ "--storage-backend",
218
+ choices=["github_actions_artifact"],
219
+ default="github_actions_artifact",
220
+ )
221
+ artifacts_parser.add_argument("--retention-days", type=int, default=14)
222
+ artifacts_parser.add_argument("--write", action="store_true")
223
+ artifacts_parser.add_argument("--force", action="store_true")
224
+ artifacts_parser.add_argument("--require-sources", action="store_true")
225
+ artifacts_parser.add_argument("--json", action="store_true")
226
+ plan_parser = subparsers.add_parser("plan")
227
+ plan_parser.add_argument("manifest", type=Path)
228
+ plan_parser.add_argument("--json", action="store_true")
229
+ args = parser.parse_args(argv)
230
+
231
+ if args.command == "artifacts":
232
+ if __package__ in {None, ""}:
233
+ module_dir = Path(__file__).resolve().parent
234
+ if module_dir.name == "code_mower": # pragma: no cover - extracted direct CLI.
235
+ sys.path.insert(0, str(module_dir.parent))
236
+ from code_mower import blind_review_artifacts
237
+ else:
238
+ sys.path.insert(0, str(module_dir.parent))
239
+ from tools import blind_review_artifacts
240
+ elif __package__ == "tools":
241
+ from tools import blind_review_artifacts
242
+ else: # pragma: no cover - exercised after package extraction.
243
+ from . import blind_review_artifacts
244
+ return blind_review_artifacts.main(
245
+ [
246
+ str(args.manifest),
247
+ "--output-dir",
248
+ args.output_dir,
249
+ "--storage-backend",
250
+ args.storage_backend,
251
+ "--retention-days",
252
+ str(args.retention_days),
253
+ *(["--write"] if args.write else []),
254
+ *(["--force"] if args.force else []),
255
+ *(["--require-sources"] if args.require_sources else []),
256
+ *(["--json"] if args.json else []),
257
+ ]
258
+ )
259
+
260
+ if args.command == "plan":
261
+ try:
262
+ plan = build_blind_review_plan(_load_manifest(args.manifest))
263
+ except ValueError as exc:
264
+ print(f"error: {exc}", file=sys.stderr)
265
+ return 1
266
+ if args.json:
267
+ print(json.dumps(plan, indent=2, sort_keys=True))
268
+ else:
269
+ print(render_blind_review_text(plan), end="")
270
+ return 0
271
+
272
+ raise AssertionError(f"unhandled blind-review command: {args.command}")
273
+
274
+
275
+ if __name__ == "__main__":
276
+ raise SystemExit(main())