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
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())
|