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/cli.py ADDED
@@ -0,0 +1,537 @@
1
+ #!/usr/bin/env python3
2
+ """Reference dispatcher for the future standalone Code Mower CLI."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ import sys
10
+ import urllib.error
11
+ import urllib.request
12
+ from collections.abc import Callable
13
+ from pathlib import Path
14
+
15
+ if __package__ in {None, ""}:
16
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
17
+
18
+ if __package__ in {None, "", "tools"}:
19
+ from tools import __version__
20
+ from tools import (
21
+ antigravity_cli_audit_pr,
22
+ blind_review_coordinator,
23
+ bootstrap as code_mower_bootstrap,
24
+ claude_audit_pr,
25
+ claude_cli_bounce,
26
+ clear_stale,
27
+ coderabbit_cli_audit_pr,
28
+ codex_audit_env_preflight,
29
+ codex_audit_pr,
30
+ codex_audit_schema_smoke,
31
+ hermes_cli_audit_pr,
32
+ code_mower_builder_experiment,
33
+ code_mower_cloud,
34
+ code_mower_config,
35
+ code_mower_calibration,
36
+ code_mower_context_packs,
37
+ code_mower_doctor,
38
+ code_mower_init,
39
+ code_mower_merge,
40
+ code_mower_next_steps,
41
+ code_mower_package,
42
+ code_mower_prompts,
43
+ code_mower_telemetry,
44
+ migration as code_mower_migration,
45
+ gemini_cli_audit_pr,
46
+ local_llm_bakeoff,
47
+ local_llm_calibration,
48
+ local_llm_audit_pr,
49
+ local_llm_profiles,
50
+ reviewer_metrics,
51
+ saas_reviewer_labeler,
52
+ trailer_comment_labeler,
53
+ )
54
+ from code_mower import checks as code_mower_checks
55
+ else: # pragma: no cover - exercised after package extraction.
56
+ from . import __version__
57
+ from . import blind_review_coordinator
58
+ from . import antigravity_cli_audit_pr
59
+ from . import bootstrap as code_mower_bootstrap
60
+ from . import builder_experiment as code_mower_builder_experiment
61
+ from . import claude_audit_pr
62
+ from . import claude_cli_bounce
63
+ from . import clear_stale
64
+ from . import cloud as code_mower_cloud
65
+ from . import coderabbit_cli_audit_pr
66
+ from . import codex_audit_env_preflight
67
+ from . import codex_audit_pr
68
+ from . import codex_audit_schema_smoke
69
+ from . import config as code_mower_config
70
+ from . import code_mower_calibration
71
+ from . import checks as code_mower_checks
72
+ from . import code_mower_context_packs
73
+ from . import code_mower_merge
74
+ from . import next_steps as code_mower_next_steps
75
+ from . import doctor as code_mower_doctor
76
+ from . import gemini_cli_audit_pr
77
+ from . import hermes_cli_audit_pr
78
+ from . import init as code_mower_init
79
+ from . import code_mower_telemetry
80
+ from . import local_llm_bakeoff
81
+ from . import local_llm_calibration
82
+ from . import local_llm_audit_pr
83
+ from . import local_llm_profiles
84
+ from . import migration as code_mower_migration
85
+ from . import package as code_mower_package
86
+ from . import prompts as code_mower_prompts
87
+ from . import reviewer_metrics
88
+ from . import saas_reviewer_labeler
89
+ from . import trailer_comment_labeler
90
+
91
+
92
+ def _has_positional_config(argv: list[str], options_with_values: set[str]) -> bool:
93
+ skip_next = False
94
+ for arg in argv:
95
+ if skip_next:
96
+ skip_next = False
97
+ continue
98
+ if arg in options_with_values:
99
+ skip_next = True
100
+ continue
101
+ if any(arg.startswith(f"{option}=") for option in options_with_values):
102
+ continue
103
+ if arg.startswith("-"):
104
+ continue
105
+ return True
106
+ return False
107
+
108
+
109
+ def _default_config_args(
110
+ argv: list[str],
111
+ default_config: str = "code-mower.yml",
112
+ options_with_values: set[str] | None = None,
113
+ ) -> list[str]:
114
+ if _has_positional_config(argv, options_with_values or set()):
115
+ return argv
116
+ return [default_config, *argv]
117
+
118
+
119
+ def _has_flag(argv: list[str], flag: str) -> bool:
120
+ return any(arg == flag for arg in argv)
121
+
122
+
123
+ def _resolve_provider_templates_path(path_text: str) -> Path:
124
+ return code_mower_package.resolve_provider_templates_path(path_text)
125
+
126
+
127
+ def _config_main(argv: list[str]) -> int:
128
+ parser = argparse.ArgumentParser(prog="code-mower config")
129
+ subparsers = parser.add_subparsers(dest="command", required=True)
130
+ validate = subparsers.add_parser("validate")
131
+ validate.add_argument("config", nargs="?", default="code-mower.yml")
132
+ plan = subparsers.add_parser("plan")
133
+ plan.add_argument("config", nargs="?", default="code-mower.yml")
134
+ plan.add_argument("--json", action="store_true")
135
+ args = parser.parse_args(argv)
136
+
137
+ if args.command == "validate":
138
+ return code_mower_config.main([args.config, "--validate-only"])
139
+ if args.command == "plan":
140
+ forwarded = [args.config]
141
+ if args.json:
142
+ forwarded.append("--json")
143
+ return code_mower_config.main(forwarded)
144
+ raise AssertionError(f"unhandled config command: {args.command}")
145
+
146
+
147
+ def _providers_main(argv: list[str]) -> int:
148
+ parser = argparse.ArgumentParser(prog="code-mower providers")
149
+ parser.add_argument(
150
+ "--provider-templates",
151
+ default=None,
152
+ )
153
+ subparsers = parser.add_subparsers(dest="command", required=True)
154
+ subparsers.add_parser("list")
155
+ show = subparsers.add_parser("show")
156
+ show.add_argument("provider")
157
+ show.add_argument("--json", action="store_true")
158
+ args = parser.parse_args(argv)
159
+
160
+ try:
161
+ provider_templates_path = (
162
+ _resolve_provider_templates_path(code_mower_package.DEFAULT_PROVIDER_TEMPLATES)
163
+ if args.provider_templates is None
164
+ else Path(args.provider_templates)
165
+ )
166
+ templates = code_mower_package.load_provider_templates(
167
+ provider_templates_path
168
+ )
169
+ except code_mower_config.ConfigError as exc:
170
+ print(f"error: {exc}", file=sys.stderr)
171
+ return 1
172
+ providers = templates["provider_templates"]
173
+
174
+ if args.command == "list":
175
+ for provider in sorted(providers):
176
+ print(provider)
177
+ return 0
178
+
179
+ if args.command == "show":
180
+ provider = providers.get(args.provider)
181
+ if not isinstance(provider, dict):
182
+ print(f"error: unknown provider: {args.provider}", file=sys.stderr)
183
+ return 1
184
+ token_env = provider.get("token_env", [])
185
+ review_hygiene = provider.get("review_hygiene", {})
186
+ if not token_env and isinstance(review_hygiene, dict) and review_hygiene.get("token_env"):
187
+ token_env = [review_hygiene["token_env"]]
188
+ data = {
189
+ "lane_id": provider.get("lane_id", args.provider),
190
+ "type": provider.get("type", ""),
191
+ "driver": provider.get("driver", ""),
192
+ "provider": provider.get("provider", ""),
193
+ "adapter": provider.get("adapter"),
194
+ "events": list(provider.get("events", [])),
195
+ "token_env": list(token_env),
196
+ "token_env_any": list(provider.get("token_env_any", [])),
197
+ "informational": bool(provider.get("informational", False)),
198
+ "merge_authority": bool(provider.get("merge_authority", False)),
199
+ "enabled_by_default": provider.get("enabled_by_default", True),
200
+ "trigger_policy": provider.get("trigger_policy", "label"),
201
+ "spend_policy": provider.get("spend_policy", "none"),
202
+ "provider_config": provider.get("provider_config", {}),
203
+ }
204
+ if args.json:
205
+ print(json.dumps(data, indent=2, sort_keys=True))
206
+ else:
207
+ print(f"{data['lane_id']}: {data['driver']} / {data['provider']}")
208
+ print(f"spend_policy: {data['spend_policy']}")
209
+ return 0
210
+
211
+ raise AssertionError(f"unhandled providers command: {args.command}")
212
+
213
+
214
+ def _fetch_local_llm_models(api_base: str, api_key: str, timeout: int = 10) -> list[str]:
215
+ request = urllib.request.Request(
216
+ f"{api_base.rstrip('/')}/models",
217
+ headers={
218
+ "Authorization": f"Bearer {api_key}",
219
+ "Content-Type": "application/json",
220
+ },
221
+ )
222
+ with urllib.request.urlopen(request, timeout=timeout) as response:
223
+ payload = json.loads(response.read().decode("utf-8"))
224
+ data = payload.get("data", []) if isinstance(payload, dict) else []
225
+ return [
226
+ str(entry.get("id"))
227
+ for entry in data
228
+ if isinstance(entry, dict) and entry.get("id")
229
+ ]
230
+
231
+
232
+ def _local_llm_main(argv: list[str]) -> int:
233
+ parser = argparse.ArgumentParser(prog="code-mower local-llm")
234
+ subparsers = parser.add_subparsers(dest="command", required=True)
235
+ profiles = subparsers.add_parser("profiles")
236
+ profiles.add_argument("--json", action="store_true")
237
+ probe = subparsers.add_parser("probe")
238
+ probe.add_argument(
239
+ "--profile",
240
+ choices=local_llm_profiles.profile_ids(),
241
+ default=None,
242
+ )
243
+ probe.add_argument(
244
+ "--api-base",
245
+ default=None,
246
+ )
247
+ probe.add_argument(
248
+ "--api-key",
249
+ default=None,
250
+ )
251
+ probe.add_argument(
252
+ "--http-timeout",
253
+ type=int,
254
+ default=None,
255
+ )
256
+ probe.add_argument("--json", action="store_true")
257
+ subparsers.add_parser("audit", add_help=False)
258
+ subparsers.add_parser("bakeoff", add_help=False)
259
+ subparsers.add_parser("calibrate", add_help=False)
260
+ args, rest = parser.parse_known_args(argv)
261
+
262
+ if args.command == "audit":
263
+ return local_llm_audit_pr.main(rest)
264
+ if args.command == "bakeoff":
265
+ return local_llm_bakeoff.main(rest)
266
+ if args.command == "calibrate":
267
+ return local_llm_calibration.main(rest)
268
+ if args.command == "profiles":
269
+ data = [profile.as_dict() for profile in local_llm_profiles.list_profiles()]
270
+ if args.json:
271
+ print(json.dumps({"profiles": data}, indent=2, sort_keys=True))
272
+ else:
273
+ for profile in data:
274
+ print(f"{profile['profile_id']}: {profile['model']} @ {profile['api_base']}")
275
+ return 0
276
+ if args.command == "probe":
277
+ try:
278
+ probe_timeout = (
279
+ args.http_timeout
280
+ if args.http_timeout is not None
281
+ else int(
282
+ os.environ.get(
283
+ "LOCAL_LLM_PROBE_HTTP_TIMEOUT",
284
+ os.environ.get("LOCAL_LLM_HTTP_TIMEOUT", 10),
285
+ )
286
+ )
287
+ )
288
+ except ValueError as exc:
289
+ print(
290
+ f"error: local LLM probe timeout must be an integer: {exc}",
291
+ file=sys.stderr,
292
+ )
293
+ return 1
294
+ try:
295
+ profile_id = args.profile or os.environ.get("LOCAL_LLM_PROFILE") or ""
296
+ profile = (
297
+ local_llm_profiles.get_profile(profile_id)
298
+ if profile_id
299
+ else None
300
+ )
301
+ except KeyError as exc:
302
+ print(f"error: {exc}", file=sys.stderr)
303
+ return 1
304
+ api_base = (
305
+ args.api_base
306
+ or os.environ.get("LOCAL_LLM_API_BASE")
307
+ or (profile.api_base if profile else local_llm_audit_pr.DEFAULT_API_BASE)
308
+ )
309
+ api_key = (
310
+ args.api_key
311
+ or os.environ.get("LOCAL_LLM_API_KEY")
312
+ or (profile.api_key if profile else local_llm_audit_pr.DEFAULT_API_KEY)
313
+ )
314
+ resolved_profile_id = profile.profile_id if profile else profile_id
315
+ try:
316
+ models = _fetch_local_llm_models(
317
+ api_base,
318
+ api_key,
319
+ probe_timeout,
320
+ )
321
+ except (OSError, urllib.error.URLError, json.JSONDecodeError) as exc:
322
+ print(f"error: local LLM probe failed: {exc}", file=sys.stderr)
323
+ return 1
324
+ payload = {
325
+ "api_base": api_base,
326
+ "profile_id": resolved_profile_id,
327
+ "models": models,
328
+ }
329
+ if args.json:
330
+ print(json.dumps(payload, indent=2, sort_keys=True))
331
+ else:
332
+ if resolved_profile_id:
333
+ print(f"profile: {resolved_profile_id}")
334
+ print(f"api_base: {api_base}")
335
+ if models:
336
+ for model in models:
337
+ print(model)
338
+ else:
339
+ print("no models reported")
340
+ return 0
341
+ raise AssertionError(f"unhandled local-llm command: {args.command}")
342
+
343
+
344
+ CommandHandler = Callable[[list[str]], int]
345
+
346
+
347
+ COMMAND_DESCRIPTIONS: dict[str, str] = {
348
+ "antigravity-cli": "Run an Antigravity/Gemini CLI structured audit lane.",
349
+ "blind-review": "Coordinate hidden/blind review artifacts.",
350
+ "bootstrap": "Bootstrap generated support files and workflow fixtures.",
351
+ "builder-experiment": "Capture builder-side experiment metadata.",
352
+ "calibration": "Create corpora, dispositions, policy, and value reports.",
353
+ "claude-audit": "Run a Claude structured audit lane.",
354
+ "claude-bounce": "Diagnose and retry Claude CLI auth with a clean subprocess env.",
355
+ "clear-stale": "Clear stale terminal audit labels after a PR head changes.",
356
+ "checks": "Detect and run a repository's native lint/test/build surface.",
357
+ "cloud": "Export or upload sanitized benchmark metadata.",
358
+ "config": "Validate or inspect a Code Mower config.",
359
+ "context-packs": "Build selective surrounding-file context packs.",
360
+ "coderabbit-cli": "Run a CodeRabbit CLI informational lane.",
361
+ "codex-audit": "Run a Codex structured audit lane.",
362
+ "codex-audit-env-preflight": "Probe Codex audit environment setup.",
363
+ "codex-audit-schema-smoke": "Smoke-test Codex audit schema parsing.",
364
+ "doctor": "Check runtime, GitHub, providers, privacy, and cloud setup.",
365
+ "gemini-cli": "Run a Gemini CLI structured audit lane.",
366
+ "hermes-cli": "Run a Hermes CLI structured audit lane.",
367
+ "init": "Render safe easy-mode setup output.",
368
+ "local-llm": "Probe and run local OpenAI-compatible model lanes.",
369
+ "migration": "Rehearse standalone package and wrapper migration paths.",
370
+ "merge-plan": "Inspect merge-readiness signals and lane labels.",
371
+ "next-steps": "Print the recommended next actions after setup.",
372
+ "package": "Build or inspect package extraction artifacts.",
373
+ "prompts": "Inspect prompt/lens customization artifacts.",
374
+ "providers": "List or inspect provider template definitions.",
375
+ "reviewer-metrics": "Compute reviewer metrics and value reports.",
376
+ "saas-reviewer-labeler": "Apply labels from hosted reviewer comments.",
377
+ "telemetry": "Inspect benchmark telemetry/event helpers.",
378
+ "trailer-comment-labeler": "Apply labels from structured audit trailers.",
379
+ }
380
+
381
+
382
+ FIRST_USER_COMMANDS = (
383
+ "init",
384
+ "doctor",
385
+ "checks",
386
+ "next-steps",
387
+ "calibration",
388
+ "reviewer-metrics",
389
+ "cloud",
390
+ "config",
391
+ )
392
+
393
+
394
+ def _init_main(argv: list[str]) -> int:
395
+ options_with_values = {"--profile", "--output-dir"}
396
+ if (
397
+ argv[:1] == ["auth"]
398
+ or _has_flag(argv, "--easy")
399
+ or _has_positional_config(argv, options_with_values)
400
+ ):
401
+ return code_mower_init.main(argv)
402
+ return code_mower_init.main(
403
+ _default_config_args(
404
+ argv,
405
+ default_config="code-mower.yml",
406
+ options_with_values=options_with_values,
407
+ )
408
+ )
409
+
410
+
411
+ def _package_main(argv: list[str]) -> int:
412
+ return code_mower_package.main(argv)
413
+
414
+
415
+ def _advanced_commands() -> tuple[str, ...]:
416
+ return tuple(command for command in COMMAND_HANDLERS if command not in FIRST_USER_COMMANDS)
417
+
418
+
419
+ def _format_command_rows(commands: tuple[str, ...]) -> list[str]:
420
+ width = max(len(command) for command in commands)
421
+ return [
422
+ f" {command:<{width}} {COMMAND_DESCRIPTIONS.get(command, '')}"
423
+ for command in commands
424
+ ]
425
+
426
+
427
+ def _top_level_help(show_all: bool) -> str:
428
+ first_user_rows = "\n".join(_format_command_rows(FIRST_USER_COMMANDS))
429
+ lines = [
430
+ "usage: code-mower [--version] <command> [args]",
431
+ "",
432
+ "Code Mower creates local-first AI peer-review and calibration loops.",
433
+ "",
434
+ "First-user commands:",
435
+ first_user_rows,
436
+ ]
437
+ if show_all:
438
+ advanced_rows = "\n".join(_format_command_rows(_advanced_commands()))
439
+ lines.extend(
440
+ [
441
+ "",
442
+ "Advanced/provider/operator commands:",
443
+ advanced_rows,
444
+ ]
445
+ )
446
+ else:
447
+ lines.extend(
448
+ [
449
+ "",
450
+ "Run code-mower --help-all to show advanced provider/operator commands.",
451
+ ]
452
+ )
453
+ lines.extend(
454
+ [
455
+ "",
456
+ "Common first run:",
457
+ " code-mower init --easy",
458
+ " code-mower doctor --preflight",
459
+ " code-mower next-steps --profile recommended",
460
+ (
461
+ " code-mower migration package-install-rehearsal "
462
+ f"--package-spec {code_mower_next_steps.current_alpha_package_spec()} --json"
463
+ ),
464
+ ]
465
+ )
466
+ return "\n".join(lines) + "\n"
467
+
468
+
469
+ COMMAND_HANDLERS: dict[str, CommandHandler] = {
470
+ "antigravity-cli": antigravity_cli_audit_pr.main,
471
+ "blind-review": blind_review_coordinator.main,
472
+ "bootstrap": code_mower_bootstrap.main,
473
+ "builder-experiment": code_mower_builder_experiment.main,
474
+ "calibration": code_mower_calibration.main,
475
+ "claude-audit": claude_audit_pr.main,
476
+ "claude-bounce": claude_cli_bounce.main,
477
+ "clear-stale": clear_stale.main,
478
+ "checks": code_mower_checks.main,
479
+ "cloud": code_mower_cloud.main,
480
+ "config": _config_main,
481
+ "context-packs": code_mower_context_packs.main,
482
+ "coderabbit-cli": coderabbit_cli_audit_pr.main,
483
+ "codex-audit": codex_audit_pr.main,
484
+ "codex-audit-env-preflight": codex_audit_env_preflight.main,
485
+ "codex-audit-schema-smoke": codex_audit_schema_smoke.main,
486
+ "doctor": code_mower_doctor.main,
487
+ "gemini-cli": gemini_cli_audit_pr.main,
488
+ "hermes-cli": hermes_cli_audit_pr.main,
489
+ "init": _init_main,
490
+ "local-llm": _local_llm_main,
491
+ "migration": code_mower_migration.main,
492
+ "merge-plan": code_mower_merge.main,
493
+ "next-steps": code_mower_next_steps.main,
494
+ "package": _package_main,
495
+ "prompts": code_mower_prompts.main,
496
+ "providers": _providers_main,
497
+ "reviewer-metrics": reviewer_metrics.main,
498
+ "saas-reviewer-labeler": saas_reviewer_labeler.main,
499
+ "telemetry": code_mower_telemetry.main,
500
+ "trailer-comment-labeler": trailer_comment_labeler.main,
501
+ }
502
+
503
+
504
+ def main(argv: list[str] | None = None) -> int:
505
+ raw_argv = list(sys.argv[1:] if argv is None else argv)
506
+ if not raw_argv or raw_argv == ["-h"] or raw_argv == ["--help"]:
507
+ print(_top_level_help(show_all=False), end="")
508
+ return 0
509
+ if raw_argv == ["--help-all"]:
510
+ print(_top_level_help(show_all=True), end="")
511
+ return 0
512
+
513
+ parser = argparse.ArgumentParser(prog="code-mower", add_help=False)
514
+ parser.add_argument("-h", "--help", action="store_true")
515
+ parser.add_argument("--help-all", action="store_true")
516
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
517
+ subparsers = parser.add_subparsers(dest="command", required=True)
518
+ for command in COMMAND_HANDLERS:
519
+ subparsers.add_parser(command, add_help=False)
520
+ args, rest = parser.parse_known_args(raw_argv)
521
+
522
+ if args.help:
523
+ print(_top_level_help(show_all=False), end="")
524
+ return 0
525
+ if args.help_all:
526
+ print(_top_level_help(show_all=True), end="")
527
+ return 0
528
+
529
+ try:
530
+ handler = COMMAND_HANDLERS[args.command]
531
+ except KeyError as exc: # pragma: no cover - argparse validates commands.
532
+ raise AssertionError(f"unhandled command: {args.command}") from exc
533
+ return handler(rest)
534
+
535
+
536
+ if __name__ == "__main__":
537
+ raise SystemExit(main())