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
code_mower/cloud.py ADDED
@@ -0,0 +1,674 @@
1
+ #!/usr/bin/env python3
2
+ """Prepare opt-in Code Mower cloud benchmark bundles."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ if __package__ in {None, ""}:
13
+ module_dir = Path(__file__).resolve().parent
14
+ sys.path.insert(0, str(module_dir.parent))
15
+ from code_mower.cloud_client import (
16
+ BUNDLE_MANIFEST_FILENAME,
17
+ EVENT_SCHEMA,
18
+ GITHUB_RUN_LIST_FIELDS,
19
+ MAX_EVENT_COUNT,
20
+ SAFE_EVENT_TYPES,
21
+ SAFE_REPORT_KINDS,
22
+ UPLOAD_SCHEMA as UPLOAD_SCHEMA,
23
+ CloudBundleError,
24
+ DEFAULT_INSTALL_ID_ENV,
25
+ DEFAULT_SETUP_INSTALL_ID,
26
+ DEFAULT_TEAM_ID_ENV,
27
+ DEFAULT_TOKEN_ENV,
28
+ DEFAULT_UPLOAD_ENDPOINT,
29
+ build_cloud_bundle,
30
+ catch_up_upload as _catch_up_upload,
31
+ default_setup_path as _default_setup_path,
32
+ build_upload_payload,
33
+ default_dogfood_reports as _default_dogfood_reports,
34
+ dogfood_upload as _dogfood_upload,
35
+ event_id_from_github_run as _event_id_from_github_run,
36
+ is_local_http_endpoint,
37
+ load_bundle_manifest as load_bundle_manifest,
38
+ load_event_file as _load_event_file,
39
+ parse_repo_sync_spec as _parse_repo_sync_spec,
40
+ parse_event_args as _parse_event_args,
41
+ post_upload_payload,
42
+ read_token_file as _read_token_file,
43
+ render_bundle_readme,
44
+ render_cloud_doctor_text,
45
+ render_setup_env,
46
+ repo_slug_from_remote as _repo_slug_from_remote,
47
+ repo_sync_output_name as _repo_sync_output_name,
48
+ repo_sync_upload as _repo_sync_upload,
49
+ resolve_setup_token as _resolve_setup_token,
50
+ reviewer_runs_upload as _reviewer_runs_upload,
51
+ run_cloud_doctor,
52
+ run_cloud_setup,
53
+ run_git as _run_git,
54
+ safe_config_stem as _safe_config_stem,
55
+ safe_event_type as _safe_event_type,
56
+ safe_kind as _safe_kind,
57
+ token_prefix as _token_prefix,
58
+ utc_now as _utc_now,
59
+ validate_metadata_payload,
60
+ write_setup_env_file,
61
+ )
62
+ from code_mower import code_mower_telemetry
63
+ else: # pragma: no cover - exercised after package extraction.
64
+ from .cloud_client import (
65
+ BUNDLE_MANIFEST_FILENAME,
66
+ EVENT_SCHEMA,
67
+ GITHUB_RUN_LIST_FIELDS,
68
+ MAX_EVENT_COUNT,
69
+ SAFE_EVENT_TYPES,
70
+ SAFE_REPORT_KINDS,
71
+ UPLOAD_SCHEMA as UPLOAD_SCHEMA,
72
+ CloudBundleError,
73
+ DEFAULT_INSTALL_ID_ENV,
74
+ DEFAULT_SETUP_INSTALL_ID,
75
+ DEFAULT_TEAM_ID_ENV,
76
+ DEFAULT_TOKEN_ENV,
77
+ DEFAULT_UPLOAD_ENDPOINT,
78
+ build_cloud_bundle,
79
+ catch_up_upload as _catch_up_upload,
80
+ default_setup_path as _default_setup_path,
81
+ build_upload_payload,
82
+ default_dogfood_reports as _default_dogfood_reports,
83
+ dogfood_upload as _dogfood_upload,
84
+ event_id_from_github_run as _event_id_from_github_run,
85
+ is_local_http_endpoint,
86
+ load_bundle_manifest as load_bundle_manifest,
87
+ load_event_file as _load_event_file,
88
+ parse_repo_sync_spec as _parse_repo_sync_spec,
89
+ parse_event_args as _parse_event_args,
90
+ post_upload_payload,
91
+ read_token_file as _read_token_file,
92
+ render_bundle_readme,
93
+ render_cloud_doctor_text,
94
+ render_setup_env,
95
+ repo_slug_from_remote as _repo_slug_from_remote,
96
+ repo_sync_output_name as _repo_sync_output_name,
97
+ repo_sync_upload as _repo_sync_upload,
98
+ resolve_setup_token as _resolve_setup_token,
99
+ reviewer_runs_upload as _reviewer_runs_upload,
100
+ run_cloud_doctor,
101
+ run_cloud_setup,
102
+ run_git as _run_git,
103
+ safe_config_stem as _safe_config_stem,
104
+ safe_event_type as _safe_event_type,
105
+ safe_kind as _safe_kind,
106
+ token_prefix as _token_prefix,
107
+ utc_now as _utc_now,
108
+ validate_metadata_payload,
109
+ write_setup_env_file,
110
+ )
111
+ from . import code_mower_telemetry
112
+
113
+
114
+ DEFAULT_OUTPUT_DIR = ".code-mower/cloud-benchmark-bundle"
115
+ DEFAULT_CATCH_UP_OUTPUT_DIR = ".code-mower/cloud-catch-up-bundle"
116
+ DEFAULT_REVIEWER_RUNS_OUTPUT_DIR = ".code-mower/reviewer-run-bundle"
117
+ DEFAULT_REPO_SYNC_OUTPUT_DIR = ".code-mower/cloud-repo-sync"
118
+
119
+ # Keep the legacy cloud.py import surface intentional while implementation
120
+ # moves into code_mower.cloud_client modules.
121
+ __all__ = [
122
+ "EVENT_SCHEMA",
123
+ "GITHUB_RUN_LIST_FIELDS",
124
+ "BUNDLE_MANIFEST_FILENAME",
125
+ "CloudBundleError",
126
+ "DEFAULT_INSTALL_ID_ENV",
127
+ "DEFAULT_SETUP_INSTALL_ID",
128
+ "DEFAULT_TEAM_ID_ENV",
129
+ "DEFAULT_TOKEN_ENV",
130
+ "DEFAULT_UPLOAD_ENDPOINT",
131
+ "DEFAULT_OUTPUT_DIR",
132
+ "DEFAULT_CATCH_UP_OUTPUT_DIR",
133
+ "DEFAULT_REVIEWER_RUNS_OUTPUT_DIR",
134
+ "DEFAULT_REPO_SYNC_OUTPUT_DIR",
135
+ "build_cloud_bundle",
136
+ "build_upload_payload",
137
+ "post_upload_payload",
138
+ "render_bundle_readme",
139
+ "render_setup_env",
140
+ "run_cloud_doctor",
141
+ "run_cloud_setup",
142
+ "validate_metadata_payload",
143
+ "write_setup_env_file",
144
+ "main",
145
+ "_catch_up_upload",
146
+ "_default_dogfood_reports",
147
+ "_default_setup_path",
148
+ "_dogfood_upload",
149
+ "_event_id_from_github_run",
150
+ "_load_event_file",
151
+ "_parse_event_args",
152
+ "_parse_repo_sync_spec",
153
+ "_read_token_file",
154
+ "_repo_slug_from_remote",
155
+ "_repo_sync_output_name",
156
+ "_repo_sync_upload",
157
+ "_resolve_setup_token",
158
+ "_reviewer_runs_upload",
159
+ "_run_git",
160
+ "_safe_config_stem",
161
+ "_safe_event_type",
162
+ "_token_prefix",
163
+ "_utc_now",
164
+ ]
165
+
166
+
167
+ def _is_local_http_endpoint(endpoint: str) -> bool:
168
+ return is_local_http_endpoint(endpoint)
169
+
170
+
171
+ def _parse_report_args(values: list[str]) -> list[tuple[Path, str]]:
172
+ reports: list[tuple[Path, str]] = []
173
+ for raw in values:
174
+ if "=" not in raw:
175
+ raise CloudBundleError(
176
+ "--report entries must use KIND=PATH, for example reviewer-metrics=metrics.json"
177
+ )
178
+ kind, path_text = raw.split("=", 1)
179
+ reports.append((Path(path_text), _safe_kind(kind)))
180
+ return reports
181
+
182
+
183
+ def main(argv: list[str] | None = None) -> int:
184
+ parser = argparse.ArgumentParser(prog="code-mower cloud")
185
+ subparsers = parser.add_subparsers(dest="command", required=True)
186
+ setup = subparsers.add_parser("setup")
187
+ setup.add_argument(
188
+ "--token",
189
+ default="",
190
+ help="team ingest token; prefer --token-stdin to avoid shell history",
191
+ )
192
+ setup.add_argument(
193
+ "--token-file",
194
+ type=Path,
195
+ default=None,
196
+ help="file containing either a raw token or CODE_MOWER_CLOUD_TOKEN assignment",
197
+ )
198
+ setup.add_argument(
199
+ "--token-stdin",
200
+ action="store_true",
201
+ help="read the team ingest token from stdin",
202
+ )
203
+ setup.add_argument("--token-env", default=DEFAULT_TOKEN_ENV)
204
+ setup.add_argument(
205
+ "--endpoint",
206
+ default=os.environ.get("CODE_MOWER_CLOUD_ENDPOINT", DEFAULT_UPLOAD_ENDPOINT),
207
+ )
208
+ setup.add_argument("--team-id", default=os.environ.get(DEFAULT_TEAM_ID_ENV, ""))
209
+ setup.add_argument(
210
+ "--install-id",
211
+ default=os.environ.get(DEFAULT_INSTALL_ID_ENV, DEFAULT_SETUP_INSTALL_ID),
212
+ )
213
+ setup.add_argument(
214
+ "--out",
215
+ type=Path,
216
+ default=None,
217
+ help="env file to write; defaults to ~/.config/code-mower/tokens/<install-id>.env",
218
+ )
219
+ setup.add_argument("--force", action="store_true")
220
+ setup.add_argument("--dry-run", action="store_true")
221
+ setup.add_argument("--json", action="store_true")
222
+ export = subparsers.add_parser("export")
223
+ export.add_argument(
224
+ "--report",
225
+ action="append",
226
+ default=[],
227
+ metavar="KIND=PATH",
228
+ help=(
229
+ "copy a shareable report into the bundle; kinds: "
230
+ + ", ".join(sorted(SAFE_REPORT_KINDS))
231
+ ),
232
+ )
233
+ export.add_argument(
234
+ "--event",
235
+ action="append",
236
+ default=[],
237
+ metavar="EVENT_TYPE=PATH",
238
+ help=(
239
+ "include structured benchmark events from JSON/JSONL; event types: "
240
+ + ", ".join(sorted(SAFE_EVENT_TYPES))
241
+ ),
242
+ )
243
+ export.add_argument("--output-dir", type=Path, default=Path(DEFAULT_OUTPUT_DIR))
244
+ export.add_argument("--repo-slug", default="")
245
+ export.add_argument("--team-id", default="")
246
+ export.add_argument("--install-id", default="")
247
+ export.add_argument("--anonymous", action="store_true")
248
+ export.add_argument("--json", action="store_true")
249
+ upload = subparsers.add_parser("upload")
250
+ upload.add_argument(
251
+ "bundle_dir",
252
+ nargs="?",
253
+ type=Path,
254
+ default=Path(DEFAULT_OUTPUT_DIR),
255
+ help="bundle directory created by `code-mower cloud export`",
256
+ )
257
+ upload.add_argument(
258
+ "--endpoint",
259
+ default=os.environ.get("CODE_MOWER_CLOUD_ENDPOINT", DEFAULT_UPLOAD_ENDPOINT),
260
+ )
261
+ upload.add_argument("--token-env", default=DEFAULT_TOKEN_ENV)
262
+ upload.add_argument("--include-reports", action="store_true")
263
+ upload.add_argument("--dry-run", action="store_true")
264
+ upload.add_argument(
265
+ "--yes",
266
+ action="store_true",
267
+ help="perform the network upload; without this, upload prints a dry-run preview",
268
+ )
269
+ upload.add_argument("--timeout", type=float, default=20.0)
270
+ upload.add_argument("--json", action="store_true")
271
+ doctor = subparsers.add_parser("doctor")
272
+ doctor.add_argument(
273
+ "bundle_dir",
274
+ nargs="?",
275
+ type=Path,
276
+ default=Path(DEFAULT_OUTPUT_DIR),
277
+ help="bundle directory created by `code-mower cloud export`",
278
+ )
279
+ doctor.add_argument(
280
+ "--endpoint",
281
+ default=os.environ.get("CODE_MOWER_CLOUD_ENDPOINT", DEFAULT_UPLOAD_ENDPOINT),
282
+ )
283
+ doctor.add_argument("--token-env", default=DEFAULT_TOKEN_ENV)
284
+ doctor.add_argument(
285
+ "--probe-service",
286
+ action="store_true",
287
+ help="perform a lightweight GET against the endpoint's /api/health route",
288
+ )
289
+ doctor.add_argument("--timeout", type=float, default=5.0)
290
+ doctor.add_argument("--json", action="store_true")
291
+ dogfood = subparsers.add_parser("dogfood")
292
+ dogfood.add_argument("--repo-path", type=Path, default=Path.cwd())
293
+ dogfood.add_argument("--output-dir", type=Path, default=Path(DEFAULT_OUTPUT_DIR))
294
+ dogfood.add_argument("--repo-slug", default="")
295
+ dogfood.add_argument("--team-id", default="")
296
+ dogfood.add_argument("--install-id", default="")
297
+ dogfood.add_argument("--source", default="local")
298
+ dogfood.add_argument(
299
+ "--report",
300
+ action="append",
301
+ default=[],
302
+ metavar="KIND=PATH",
303
+ help="override default dogfood reports with explicit KIND=PATH entries",
304
+ )
305
+ dogfood.add_argument(
306
+ "--event",
307
+ action="append",
308
+ default=[],
309
+ metavar="EVENT_TYPE=PATH",
310
+ help="include additional structured benchmark events from JSON/JSONL",
311
+ )
312
+ dogfood.add_argument(
313
+ "--endpoint",
314
+ default=os.environ.get("CODE_MOWER_CLOUD_ENDPOINT", DEFAULT_UPLOAD_ENDPOINT),
315
+ )
316
+ dogfood.add_argument("--token-env", default=DEFAULT_TOKEN_ENV)
317
+ dogfood.add_argument("--include-reports", action="store_true")
318
+ dogfood.add_argument(
319
+ "--yes",
320
+ action="store_true",
321
+ help="perform the network upload; without this, dogfood is a dry run",
322
+ )
323
+ dogfood.add_argument("--timeout", type=float, default=20.0)
324
+ dogfood.add_argument("--json", action="store_true")
325
+ catch_up = subparsers.add_parser("catch-up")
326
+ catch_up.add_argument("--repo-path", type=Path, default=Path.cwd())
327
+ catch_up.add_argument(
328
+ "--output-dir",
329
+ type=Path,
330
+ default=Path(DEFAULT_CATCH_UP_OUTPUT_DIR),
331
+ )
332
+ catch_up.add_argument("--repo-slug", default="")
333
+ catch_up.add_argument("--team-id", default="")
334
+ catch_up.add_argument("--install-id", default="")
335
+ catch_up.add_argument("--source", default="github-actions-catch-up")
336
+ catch_up.add_argument(
337
+ "--limit",
338
+ type=int,
339
+ default=50,
340
+ help=f"number of recent GitHub Actions runs to include; max {MAX_EVENT_COUNT}",
341
+ )
342
+ catch_up.add_argument(
343
+ "--include-git-ref",
344
+ action="store_true",
345
+ help="include workflow head branch and SHA in metadata",
346
+ )
347
+ catch_up.add_argument(
348
+ "--endpoint",
349
+ default=os.environ.get("CODE_MOWER_CLOUD_ENDPOINT", DEFAULT_UPLOAD_ENDPOINT),
350
+ )
351
+ catch_up.add_argument("--token-env", default=DEFAULT_TOKEN_ENV)
352
+ catch_up.add_argument(
353
+ "--yes",
354
+ action="store_true",
355
+ help="perform the network upload; without this, catch-up is a dry run",
356
+ )
357
+ catch_up.add_argument("--timeout", type=float, default=20.0)
358
+ catch_up.add_argument("--json", action="store_true")
359
+ reviewer_runs = subparsers.add_parser("reviewer-runs")
360
+ reviewer_runs.add_argument("--repo-path", type=Path, default=Path.cwd())
361
+ reviewer_runs.add_argument(
362
+ "--verdicts",
363
+ type=Path,
364
+ default=code_mower_telemetry.default_verdict_artifact_dir(),
365
+ help="verdict artifact file or directory",
366
+ )
367
+ reviewer_runs.add_argument(
368
+ "--output-dir",
369
+ type=Path,
370
+ default=Path(DEFAULT_REVIEWER_RUNS_OUTPUT_DIR),
371
+ )
372
+ reviewer_runs.add_argument("--repo-slug", default="")
373
+ reviewer_runs.add_argument("--team-id", default="")
374
+ reviewer_runs.add_argument("--install-id", default="")
375
+ reviewer_runs.add_argument(
376
+ "--limit",
377
+ type=int,
378
+ default=MAX_EVENT_COUNT,
379
+ help=f"number of verdict artifacts to include; max {MAX_EVENT_COUNT}",
380
+ )
381
+ reviewer_runs.add_argument(
382
+ "--include-git-ref",
383
+ action="store_true",
384
+ help="include verdict head SHA metadata",
385
+ )
386
+ reviewer_runs.add_argument(
387
+ "--endpoint",
388
+ default=os.environ.get("CODE_MOWER_CLOUD_ENDPOINT", DEFAULT_UPLOAD_ENDPOINT),
389
+ )
390
+ reviewer_runs.add_argument("--token-env", default=DEFAULT_TOKEN_ENV)
391
+ reviewer_runs.add_argument(
392
+ "--yes",
393
+ action="store_true",
394
+ help="perform the network upload; without this, reviewer-runs is a dry run",
395
+ )
396
+ reviewer_runs.add_argument("--timeout", type=float, default=20.0)
397
+ reviewer_runs.add_argument("--json", action="store_true")
398
+ repo_sync = subparsers.add_parser("repo-sync")
399
+ repo_sync.add_argument(
400
+ "--repo",
401
+ action="append",
402
+ required=True,
403
+ default=[],
404
+ metavar="[OWNER/REPO=]PATH",
405
+ help="repository path to sync; optionally prefix with OWNER/REPO=",
406
+ )
407
+ repo_sync.add_argument(
408
+ "--mode",
409
+ action="append",
410
+ choices=("dogfood", "catch-up", "reviewer-runs"),
411
+ default=[],
412
+ help="sync mode to run; repeatable, defaults to dogfood and reviewer-runs",
413
+ )
414
+ repo_sync.add_argument(
415
+ "--output-dir",
416
+ type=Path,
417
+ default=Path(DEFAULT_REPO_SYNC_OUTPUT_DIR),
418
+ )
419
+ repo_sync.add_argument("--team-id", default="")
420
+ repo_sync.add_argument("--install-id", default="")
421
+ repo_sync.add_argument("--source-prefix", default="local-repo-sync")
422
+ repo_sync.add_argument(
423
+ "--limit",
424
+ type=int,
425
+ default=MAX_EVENT_COUNT,
426
+ help=f"number of catch-up/reviewer-run events per repo; max {MAX_EVENT_COUNT}",
427
+ )
428
+ repo_sync.add_argument(
429
+ "--include-git-ref",
430
+ action="store_true",
431
+ help="include workflow branches/head SHAs and verdict head SHAs",
432
+ )
433
+ repo_sync.add_argument(
434
+ "--endpoint",
435
+ default=os.environ.get("CODE_MOWER_CLOUD_ENDPOINT", DEFAULT_UPLOAD_ENDPOINT),
436
+ )
437
+ repo_sync.add_argument("--token-env", default=DEFAULT_TOKEN_ENV)
438
+ repo_sync.add_argument("--include-reports", action="store_true")
439
+ repo_sync.add_argument(
440
+ "--yes",
441
+ action="store_true",
442
+ help="perform network uploads; without this, repo-sync is a dry run",
443
+ )
444
+ repo_sync.add_argument("--timeout", type=float, default=20.0)
445
+ repo_sync.add_argument("--json", action="store_true")
446
+ args = parser.parse_args(argv)
447
+
448
+ try:
449
+ if args.command == "setup":
450
+ result = run_cloud_setup(
451
+ token=args.token,
452
+ token_file=args.token_file,
453
+ token_stdin=args.token_stdin,
454
+ token_env=args.token_env,
455
+ endpoint=args.endpoint,
456
+ team_id=args.team_id,
457
+ install_id=args.install_id,
458
+ out=args.out,
459
+ force=args.force,
460
+ dry_run=args.dry_run,
461
+ )
462
+ if args.json:
463
+ print(json.dumps(result, indent=2, sort_keys=True))
464
+ else:
465
+ print("Code Mower cloud setup")
466
+ print(f"Status: {result['status']}")
467
+ print(f"Path: {result['path']}")
468
+ print(f"Endpoint: {result['endpoint']}")
469
+ if result["team_id"]:
470
+ print(f"Team: {result['team_id']}")
471
+ print(f"Install: {result['install_id']}")
472
+ print(f"Token: {result['token_prefix']}")
473
+ if result["status"] == "written":
474
+ print(f"Load it with: {result['shell']}")
475
+ return 0
476
+ if args.command == "export":
477
+ payload = build_cloud_bundle(
478
+ reports=_parse_report_args(args.report),
479
+ events=_parse_event_args(args.event),
480
+ output_dir=args.output_dir,
481
+ repo_slug=args.repo_slug,
482
+ team_id=args.team_id,
483
+ install_id=args.install_id,
484
+ anonymous=args.anonymous,
485
+ )
486
+ if args.json:
487
+ print(json.dumps(payload, indent=2, sort_keys=True))
488
+ else:
489
+ print("Code Mower cloud benchmark bundle")
490
+ print(f"Output: {payload['output_dir']}")
491
+ print(f"Manifest: {payload['manifest']}")
492
+ print("Upload: local export only")
493
+ return 0
494
+ if args.command == "upload":
495
+ payload = build_upload_payload(
496
+ bundle_dir=args.bundle_dir,
497
+ include_reports=args.include_reports,
498
+ )
499
+ dry_run = args.dry_run or not args.yes
500
+ if dry_run:
501
+ preview = {
502
+ "mode": "cloud-upload-dry-run",
503
+ "endpoint": args.endpoint,
504
+ "would_upload": False,
505
+ "requires_yes": not args.yes,
506
+ "upload_mode": payload["upload_mode"],
507
+ "report_count": len(payload["reports"]),
508
+ "event_count": len(payload["events"]),
509
+ "privacy_mode": payload["privacy_mode"],
510
+ "excluded_content": payload["excluded_content"],
511
+ }
512
+ if args.json:
513
+ print(json.dumps(preview, indent=2, sort_keys=True))
514
+ else:
515
+ print("Code Mower cloud upload dry run")
516
+ print(f"Endpoint: {preview['endpoint']}")
517
+ print(f"Mode: {preview['upload_mode']}")
518
+ print(f"Reports: {preview['report_count']}")
519
+ print(f"Events: {preview['event_count']}")
520
+ print("Network: skipped (pass --yes to upload)")
521
+ return 0
522
+ token = os.environ.get(args.token_env, "")
523
+ if not token and not _is_local_http_endpoint(args.endpoint):
524
+ raise CloudBundleError(
525
+ f"{args.token_env} is not set; refusing non-local upload without a token"
526
+ )
527
+ result = post_upload_payload(
528
+ payload=payload,
529
+ endpoint=args.endpoint,
530
+ token=token,
531
+ timeout=args.timeout,
532
+ )
533
+ if args.json:
534
+ print(json.dumps(result, indent=2, sort_keys=True))
535
+ else:
536
+ print("Code Mower cloud upload complete")
537
+ print(f"Endpoint: {result['endpoint']}")
538
+ print(f"Status: {result['status']}")
539
+ return 0
540
+ if args.command == "doctor":
541
+ report = run_cloud_doctor(
542
+ bundle_dir=args.bundle_dir,
543
+ endpoint=args.endpoint,
544
+ token_env=args.token_env,
545
+ probe_service=args.probe_service,
546
+ timeout=args.timeout,
547
+ )
548
+ if args.json:
549
+ print(json.dumps(report, indent=2, sort_keys=True))
550
+ else:
551
+ print(render_cloud_doctor_text(report), end="")
552
+ return 1 if report["failures"] else 0
553
+ if args.command == "dogfood":
554
+ result = _dogfood_upload(
555
+ repo_path=args.repo_path,
556
+ output_dir=args.output_dir,
557
+ reports=_parse_report_args(args.report),
558
+ events=_parse_event_args(args.event),
559
+ repo_slug=args.repo_slug,
560
+ team_id=args.team_id,
561
+ install_id=args.install_id,
562
+ source=args.source,
563
+ endpoint=args.endpoint,
564
+ token_env=args.token_env,
565
+ include_reports=args.include_reports,
566
+ yes=args.yes,
567
+ timeout=args.timeout,
568
+ )
569
+ if args.json:
570
+ print(json.dumps(result, indent=2, sort_keys=True))
571
+ else:
572
+ print("Code Mower cloud dogfood")
573
+ print(f"Status: {result['status']}")
574
+ print(f"Bundle: {result['export']['output_dir']}")
575
+ print(f"Reports: {len(result['export']['included_reports'])}")
576
+ print(f"Events: {result['export']['event_count']}")
577
+ if result["status"] == "uploaded":
578
+ print(f"Upload status: {result['upload']['status']}")
579
+ elif result["status"] == "dry_run":
580
+ print("Network: skipped (pass --yes to upload)")
581
+ return 1 if result["status"] == "doctor_failed" else 0
582
+ if args.command == "catch-up":
583
+ result = _catch_up_upload(
584
+ repo_path=args.repo_path,
585
+ output_dir=args.output_dir,
586
+ repo_slug=args.repo_slug,
587
+ team_id=args.team_id,
588
+ install_id=args.install_id,
589
+ source=args.source,
590
+ limit=args.limit,
591
+ endpoint=args.endpoint,
592
+ token_env=args.token_env,
593
+ yes=args.yes,
594
+ timeout=args.timeout,
595
+ include_git_ref=args.include_git_ref,
596
+ )
597
+ if args.json:
598
+ print(json.dumps(result, indent=2, sort_keys=True))
599
+ else:
600
+ print("Code Mower cloud catch-up")
601
+ print(f"Status: {result['status']}")
602
+ print(f"Repository: {result['repo_slug']}")
603
+ print(f"Runs: {result['run_count']}")
604
+ print(f"Bundle: {result['export']['output_dir']}")
605
+ print(f"Events: {result['export']['event_count']}")
606
+ if result["status"] == "uploaded":
607
+ print(f"Upload status: {result['upload']['status']}")
608
+ elif result["status"] == "dry_run":
609
+ print("Network: skipped (pass --yes to upload)")
610
+ return 1 if result["status"] == "doctor_failed" else 0
611
+ if args.command == "reviewer-runs":
612
+ result = _reviewer_runs_upload(
613
+ repo_path=args.repo_path,
614
+ verdicts=args.verdicts,
615
+ output_dir=args.output_dir,
616
+ repo_slug=args.repo_slug,
617
+ team_id=args.team_id,
618
+ install_id=args.install_id,
619
+ limit=args.limit,
620
+ endpoint=args.endpoint,
621
+ token_env=args.token_env,
622
+ yes=args.yes,
623
+ timeout=args.timeout,
624
+ include_git_ref=args.include_git_ref,
625
+ )
626
+ if args.json:
627
+ print(json.dumps(result, indent=2, sort_keys=True))
628
+ else:
629
+ print("Code Mower cloud reviewer runs")
630
+ print(f"Status: {result['status']}")
631
+ print(f"Repository: {result['repo_slug']}")
632
+ print(f"Events: {result['event_count']}")
633
+ if result["status"] == "no_events":
634
+ print(f"Verdicts: {result['verdicts']}")
635
+ else:
636
+ print(f"Bundle: {result['export']['output_dir']}")
637
+ if result["status"] == "uploaded":
638
+ print(f"Upload status: {result['upload']['status']}")
639
+ elif result["status"] == "dry_run":
640
+ print("Network: skipped (pass --yes to upload)")
641
+ return 1 if result["status"] == "doctor_failed" else 0
642
+ if args.command == "repo-sync":
643
+ result = _repo_sync_upload(
644
+ repo_specs=args.repo,
645
+ output_dir=args.output_dir,
646
+ modes=args.mode,
647
+ team_id=args.team_id,
648
+ install_id=args.install_id,
649
+ source_prefix=args.source_prefix,
650
+ limit=args.limit,
651
+ endpoint=args.endpoint,
652
+ token_env=args.token_env,
653
+ include_reports=args.include_reports,
654
+ include_git_ref=args.include_git_ref,
655
+ yes=args.yes,
656
+ timeout=args.timeout,
657
+ )
658
+ if args.json:
659
+ print(json.dumps(result, indent=2, sort_keys=True))
660
+ else:
661
+ print("Code Mower cloud repo sync")
662
+ print(f"Status: {result['status']}")
663
+ print(f"Repositories: {result['repo_count']}")
664
+ print(f"Steps: {result['step_count']}")
665
+ print(f"Errors: {result['error_count']}")
666
+ return 0 if result["error_count"] == 0 else 1
667
+ except CloudBundleError as exc:
668
+ print(f"error: {exc}", file=sys.stderr)
669
+ return 1
670
+ raise AssertionError(f"unhandled cloud command: {args.command}")
671
+
672
+
673
+ if __name__ == "__main__":
674
+ raise SystemExit(main())