code-mower 0.5.0b5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. code_mower/__init__.py +3 -0
  2. code_mower/adapters/__init__.py +39 -0
  3. code_mower/adapters/_base.py +148 -0
  4. code_mower/adapters/cursor_bugbot.py +97 -0
  5. code_mower/adapters/gitar.py +111 -0
  6. code_mower/adapters/greptile.py +191 -0
  7. code_mower/adapters/qodo.py +140 -0
  8. code_mower/antigravity_cli_audit_pr.py +241 -0
  9. code_mower/audit_handoff_log.py +345 -0
  10. code_mower/audit_labeler_lib.py +311 -0
  11. code_mower/audit_progress.py +203 -0
  12. code_mower/blind_review_artifacts.py +562 -0
  13. code_mower/blind_review_coordinator.py +276 -0
  14. code_mower/bootstrap.py +524 -0
  15. code_mower/builder_experiment.py +539 -0
  16. code_mower/calibration/__init__.py +181 -0
  17. code_mower/calibration/arms.py +267 -0
  18. code_mower/calibration/auto_discovery.py +348 -0
  19. code_mower/calibration/commands.py +192 -0
  20. code_mower/calibration/context_inputs.py +199 -0
  21. code_mower/calibration/corpus.py +91 -0
  22. code_mower/calibration/evidence.py +20 -0
  23. code_mower/calibration/evidence_report.py +360 -0
  24. code_mower/calibration/identity.py +27 -0
  25. code_mower/calibration/metrics.py +18 -0
  26. code_mower/calibration/overlap.py +103 -0
  27. code_mower/calibration/planning.py +309 -0
  28. code_mower/calibration/policy.py +207 -0
  29. code_mower/calibration/results.py +315 -0
  30. code_mower/calibration/run_results.py +147 -0
  31. code_mower/calibration/run_status.py +64 -0
  32. code_mower/calibration/runner.py +360 -0
  33. code_mower/calibration/truth.py +188 -0
  34. code_mower/calibration/value_report.py +142 -0
  35. code_mower/checks.py +402 -0
  36. code_mower/claude_audit_pr.py +1126 -0
  37. code_mower/claude_cli_bounce.py +303 -0
  38. code_mower/claude_cli_environment.py +73 -0
  39. code_mower/clear_stale.py +374 -0
  40. code_mower/cli.py +537 -0
  41. code_mower/cloud.py +674 -0
  42. code_mower/cloud_client/__init__.py +173 -0
  43. code_mower/cloud_client/bundle.py +155 -0
  44. code_mower/cloud_client/doctor.py +206 -0
  45. code_mower/cloud_client/dogfood.py +78 -0
  46. code_mower/cloud_client/endpoints.py +114 -0
  47. code_mower/cloud_client/errors.py +7 -0
  48. code_mower/cloud_client/events.py +278 -0
  49. code_mower/cloud_client/export.py +272 -0
  50. code_mower/cloud_client/git_metadata.py +46 -0
  51. code_mower/cloud_client/manifest.py +39 -0
  52. code_mower/cloud_client/operations.py +448 -0
  53. code_mower/cloud_client/reports.py +45 -0
  54. code_mower/cloud_client/setup.py +205 -0
  55. code_mower/cloud_client/upload.py +97 -0
  56. code_mower/code_mower_calibration.py +598 -0
  57. code_mower/code_mower_context_packs.py +591 -0
  58. code_mower/code_mower_merge.py +227 -0
  59. code_mower/code_mower_telemetry.py +561 -0
  60. code_mower/coderabbit_cli_audit_pr.py +526 -0
  61. code_mower/codex_audit_env_preflight.py +220 -0
  62. code_mower/codex_audit_pr.py +1738 -0
  63. code_mower/codex_audit_schema_smoke.py +160 -0
  64. code_mower/codex_audit_verdict.schema.json +44 -0
  65. code_mower/config.py +655 -0
  66. code_mower/doctor.py +161 -0
  67. code_mower/doctor_checks/__init__.py +104 -0
  68. code_mower/doctor_checks/cloud.py +129 -0
  69. code_mower/doctor_checks/common.py +215 -0
  70. code_mower/doctor_checks/github.py +128 -0
  71. code_mower/doctor_checks/github_actions.py +11 -0
  72. code_mower/doctor_checks/github_actions_cost.py +99 -0
  73. code_mower/doctor_checks/github_actions_cost_summary.py +111 -0
  74. code_mower/doctor_checks/github_actions_failure_annotations.py +27 -0
  75. code_mower/doctor_checks/github_actions_failure_models.py +47 -0
  76. code_mower/doctor_checks/github_actions_failure_scan.py +200 -0
  77. code_mower/doctor_checks/github_actions_failure_selection.py +63 -0
  78. code_mower/doctor_checks/github_actions_failures.py +103 -0
  79. code_mower/doctor_checks/github_actions_permissions.py +55 -0
  80. code_mower/doctor_checks/github_api.py +79 -0
  81. code_mower/doctor_checks/github_branch.py +56 -0
  82. code_mower/doctor_checks/github_config.py +25 -0
  83. code_mower/doctor_checks/github_provider.py +61 -0
  84. code_mower/doctor_checks/github_repo.py +120 -0
  85. code_mower/doctor_checks/groups.py +36 -0
  86. code_mower/doctor_checks/models.py +96 -0
  87. code_mower/doctor_checks/output.py +86 -0
  88. code_mower/doctor_checks/presets.py +64 -0
  89. code_mower/doctor_checks/privacy.py +20 -0
  90. code_mower/doctor_checks/provider_api_model.py +138 -0
  91. code_mower/doctor_checks/provider_api_model_openai.py +29 -0
  92. code_mower/doctor_checks/provider_api_model_profiles.py +137 -0
  93. code_mower/doctor_checks/provider_env.py +113 -0
  94. code_mower/doctor_checks/provider_env_required.py +56 -0
  95. code_mower/doctor_checks/provider_env_tokens.py +100 -0
  96. code_mower/doctor_checks/provider_local_cli.py +162 -0
  97. code_mower/doctor_checks/provider_local_cli_commands.py +47 -0
  98. code_mower/doctor_checks/provider_local_cli_probe_config.py +70 -0
  99. code_mower/doctor_checks/provider_probe.py +20 -0
  100. code_mower/doctor_checks/provider_probe_auth.py +52 -0
  101. code_mower/doctor_checks/provider_probe_evaluation.py +109 -0
  102. code_mower/doctor_checks/provider_probe_json.py +45 -0
  103. code_mower/doctor_checks/provider_probe_remediation.py +39 -0
  104. code_mower/doctor_checks/providers.py +159 -0
  105. code_mower/doctor_checks/registry.py +69 -0
  106. code_mower/doctor_checks/runner.py +188 -0
  107. code_mower/doctor_checks/runtime.py +89 -0
  108. code_mower/doctor_checks/runtime_github_auth.py +148 -0
  109. code_mower/gemini_cli_audit_pr.py +897 -0
  110. code_mower/hermes_cli_audit_pr.py +436 -0
  111. code_mower/init.py +888 -0
  112. code_mower/lane_configs/__init__.py +37 -0
  113. code_mower/lane_configs/aider.py +32 -0
  114. code_mower/lane_configs/antigravity_cli.py +35 -0
  115. code_mower/lane_configs/claude.py +35 -0
  116. code_mower/lane_configs/codex.py +32 -0
  117. code_mower/lane_configs/devin.py +33 -0
  118. code_mower/lane_configs/gemini_cli.py +35 -0
  119. code_mower/lane_configs/hermes_cli.py +35 -0
  120. code_mower/lane_configs/local_llm.py +31 -0
  121. code_mower/local_llm_audit_pr.py +1364 -0
  122. code_mower/local_llm_bakeoff.py +458 -0
  123. code_mower/local_llm_calibration.py +441 -0
  124. code_mower/local_llm_profiles.py +66 -0
  125. code_mower/migration.py +508 -0
  126. code_mower/migration_install.py +292 -0
  127. code_mower/migration_mirror.py +392 -0
  128. code_mower/migration_readiness.py +237 -0
  129. code_mower/migration_rehearsal.py +718 -0
  130. code_mower/next_steps.py +441 -0
  131. code_mower/package.py +673 -0
  132. code_mower/package_content.py +444 -0
  133. code_mower/package_manifest.py +452 -0
  134. code_mower/package_paths.py +53 -0
  135. code_mower/package_rendering.py +90 -0
  136. code_mower/package_static.py +585 -0
  137. code_mower/prompts.py +267 -0
  138. code_mower/provider_registry.py +469 -0
  139. code_mower/provider_runners/__init__.py +60 -0
  140. code_mower/provider_runners/comments.py +31 -0
  141. code_mower/provider_runners/git.py +46 -0
  142. code_mower/provider_runners/github_auth.py +61 -0
  143. code_mower/provider_runners/github_pr.py +120 -0
  144. code_mower/provider_runners/process.py +58 -0
  145. code_mower/provider_runners/repo_paths.py +23 -0
  146. code_mower/provider_runners/text_schema.py +41 -0
  147. code_mower/provider_runners/verdict_artifacts.py +103 -0
  148. code_mower/provider_runners/workspace.py +57 -0
  149. code_mower/release_readiness.py +549 -0
  150. code_mower/reviewer_metrics.py +389 -0
  151. code_mower/saas_reviewer_labeler.py +809 -0
  152. code_mower/secrets.py +89 -0
  153. code_mower/templates/builder-experiment.example.json +55 -0
  154. code_mower/templates/calibration-corpus.example.json +129 -0
  155. code_mower/templates/calibration-corpus.json +129 -0
  156. code_mower/templates/code-mower.example.yml +423 -0
  157. code_mower/templates/context-packs.example.json +150 -0
  158. code_mower/templates/lane_prompts/base-audit.md +22 -0
  159. code_mower/templates/lane_prompts/calibration-policy.md +21 -0
  160. code_mower/templates/lane_prompts/context-driven-quality.md +21 -0
  161. code_mower/templates/lane_prompts/docs-design.md +12 -0
  162. code_mower/templates/lane_prompts/generic-programming.md +21 -0
  163. code_mower/templates/lane_prompts/operability.md +22 -0
  164. code_mower/templates/lane_prompts/package-runtime.md +12 -0
  165. code_mower/templates/lane_prompts/security-threat-model.md +22 -0
  166. code_mower/templates/product-support/code_mower +216 -0
  167. code_mower/templates/product-support/code_mower_standalone_pin.env +7 -0
  168. code_mower/templates/product-support/code_mower_standalone_shadow.sh +151 -0
  169. code_mower/templates/product-support/run_claude_audit_pr.sh +32 -0
  170. code_mower/templates/product-support/run_codex_audit_pr.sh +32 -0
  171. code_mower/templates/product-support/safe_gh_comment.py +96 -0
  172. code_mower/templates/providers.yml +454 -0
  173. code_mower/templates/reviewer-spend.example.json +28 -0
  174. code_mower/templates/reviewer-value-report.example.md +20 -0
  175. code_mower/templates/workflows/private-standalone-shadow.yml.j2 +106 -0
  176. code_mower/templates/workflows/review-clear-stale.yml.j2 +83 -0
  177. code_mower/trailer_comment_labeler.py +207 -0
  178. code_mower/versioning.py +32 -0
  179. code_mower-0.5.0b5.dist-info/METADATA +302 -0
  180. code_mower-0.5.0b5.dist-info/RECORD +185 -0
  181. code_mower-0.5.0b5.dist-info/WHEEL +5 -0
  182. code_mower-0.5.0b5.dist-info/entry_points.txt +2 -0
  183. code_mower-0.5.0b5.dist-info/licenses/LICENSE +202 -0
  184. code_mower-0.5.0b5.dist-info/licenses/NOTICE +10 -0
  185. code_mower-0.5.0b5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,140 @@
1
+ """Qodo adapter for the generic SaaS reviewer labeler.
2
+
3
+ Qodo is a paid hosted reviewer in the reference repos, so this adapter is
4
+ strictly opt-in. It only classifies comments from the trusted Qodo bot on PRs
5
+ that already carry a Qodo audit label.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from dataclasses import dataclass
12
+ from typing import Any, Optional
13
+
14
+ try:
15
+ from ._base import SaaSReviewerAdapter
16
+ except ImportError: # pragma: no cover - direct `python tools/foo.py` execution
17
+ try:
18
+ from tools.adapters._base import SaaSReviewerAdapter
19
+ except ImportError:
20
+ from adapters._base import SaaSReviewerAdapter
21
+
22
+
23
+ _QODO_REVIEW_RE = re.compile(r"\bCode Review by Qodo\b", re.IGNORECASE)
24
+ _QODO_TRANSIENT_FAILURE_RE = re.compile(
25
+ r"sorry,\s+something\s+went\s+wrong|could\s+not\s+complete|try\s+again",
26
+ re.IGNORECASE,
27
+ )
28
+ _COUNT_RE = re.compile(
29
+ r"(?P<name>Bugs|Rule violations|Cross-repo conflicts)\s*\((?P<count>\d+)\)",
30
+ re.IGNORECASE,
31
+ )
32
+ _EXPECTED_COUNT_KEYS = frozenset({"bugs", "rule violations", "cross-repo conflicts"})
33
+ _CODE_FENCE_RE = re.compile(r"(`+).*?\1|~~~.*?~~~", re.DOTALL)
34
+
35
+
36
+ def _strip_code(text: str) -> str:
37
+ return _CODE_FENCE_RE.sub(lambda match: re.sub(r"[^\n]", " ", match.group(0)), text)
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class QodoVerdict:
42
+ bugs: int = 0
43
+ rule_violations: int = 0
44
+ cross_repo_conflicts: int = 0
45
+ has_review_marker: bool = False
46
+ has_counts: bool = False
47
+ transient_failure: bool = False
48
+
49
+ @property
50
+ def blocker_count(self) -> int:
51
+ return self.bugs + self.rule_violations + self.cross_repo_conflicts
52
+
53
+ def classify(self) -> Optional[str]:
54
+ if self.transient_failure and not self.has_counts:
55
+ return "needs"
56
+ if not self.has_review_marker:
57
+ return None
58
+ if not self.has_counts:
59
+ return "needs"
60
+ if self.blocker_count > 0:
61
+ return "blocked"
62
+ return "done"
63
+
64
+
65
+ def parse_qodo_comment(comment_body: str) -> QodoVerdict:
66
+ body_without_code = _strip_code(comment_body)
67
+ counts = {
68
+ match.group("name").lower(): int(match.group("count"))
69
+ for match in _COUNT_RE.finditer(body_without_code)
70
+ }
71
+ return QodoVerdict(
72
+ bugs=counts.get("bugs", 0),
73
+ rule_violations=counts.get("rule violations", 0),
74
+ cross_repo_conflicts=counts.get("cross-repo conflicts", 0),
75
+ has_review_marker=bool(_QODO_REVIEW_RE.search(body_without_code)),
76
+ has_counts=_EXPECTED_COUNT_KEYS.issubset(counts.keys()),
77
+ transient_failure=bool(_QODO_TRANSIENT_FAILURE_RE.search(body_without_code)),
78
+ )
79
+
80
+
81
+ class QodoAdapter(SaaSReviewerAdapter):
82
+ @property
83
+ def name(self) -> str:
84
+ return "qodo"
85
+
86
+ @property
87
+ def label_prefix(self) -> str:
88
+ return "qodo-audit"
89
+
90
+ @property
91
+ def bot_usernames(self) -> frozenset[str]:
92
+ return frozenset({"qodo-code-review[bot]", "qodo-code-review"})
93
+
94
+ @property
95
+ def bot_authors_env_var(self) -> str:
96
+ return "QODO_BOT_AUTHORS"
97
+
98
+ @property
99
+ def event_type(self) -> str:
100
+ return "issue_comment"
101
+
102
+ @property
103
+ def opt_in_required(self) -> bool:
104
+ return True
105
+
106
+ @property
107
+ def opt_in_label_names(self) -> frozenset[str]:
108
+ return frozenset({self.needs_label, self.done_label, self.blocked_label})
109
+
110
+ @property
111
+ def opt_in_label_prefixes(self) -> frozenset[str]:
112
+ return frozenset({"qodo-audit-"})
113
+
114
+ @property
115
+ def token_env_vars(self) -> tuple[str, ...]:
116
+ return ("QODO_AUDIT_LABEL_TOKEN", "GITHUB_TOKEN")
117
+
118
+ def classify_verdict(self, event: dict[str, Any], **kwargs: Any) -> Optional[str]:
119
+ return parse_qodo_comment(kwargs.get("comment_body") or "").classify()
120
+
121
+ def label_reason(self, status: str, event: dict[str, Any], **kwargs: Any) -> str:
122
+ verdict = parse_qodo_comment(kwargs.get("comment_body") or "")
123
+ if verdict.transient_failure and not verdict.has_counts:
124
+ return "Qodo reported a transient review failure; re-queue for a fresh pass"
125
+ if status == "needs":
126
+ return "Qodo review marker found but verdict counts were not parseable"
127
+ if status == "blocked":
128
+ return (
129
+ "Qodo found blocking findings "
130
+ f"(bugs={verdict.bugs}, rule_violations={verdict.rule_violations}, "
131
+ f"cross_repo_conflicts={verdict.cross_repo_conflicts})"
132
+ )
133
+ return (
134
+ "Qodo audit clean "
135
+ f"(bugs={verdict.bugs}, rule_violations={verdict.rule_violations}, "
136
+ f"cross_repo_conflicts={verdict.cross_repo_conflicts})"
137
+ )
138
+
139
+
140
+ ADAPTER = QodoAdapter()
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/env python3
2
+ """Run Antigravity CLI as an informational Code Mower calibration reviewer."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ import shutil
10
+ import subprocess
11
+ import sys
12
+ import urllib.error
13
+ from pathlib import Path
14
+
15
+ if __package__ in {None, ""}:
16
+ module_dir = Path(__file__).resolve().parent
17
+ sys.path.insert(0, str(module_dir.parent))
18
+ if module_dir.name == "code_mower": # pragma: no cover - extracted direct CLI.
19
+ from code_mower import gemini_cli_audit_pr
20
+ from code_mower import prompts as code_mower_prompts
21
+ else:
22
+ from tools import gemini_cli_audit_pr, code_mower_prompts
23
+ elif __package__ == "tools":
24
+ from tools import gemini_cli_audit_pr, code_mower_prompts
25
+ else: # pragma: no cover - exercised after package extraction.
26
+ from . import gemini_cli_audit_pr
27
+ from . import prompts as code_mower_prompts
28
+
29
+
30
+ DEFAULT_ANTIGRAVITY_COMMAND = "agy"
31
+ DEFAULT_ANTIGRAVITY_MODE = "antigravity-cli-audit"
32
+ DEFAULT_ANTIGRAVITY_OUTPUT_STEM = "antigravity-cli"
33
+ DEFAULT_ANTIGRAVITY_DISPLAY_NAME = "Antigravity CLI"
34
+ ANTIGRAVITY_SETTINGS_SUBDIRS = (".gemini", ".gemini/antigravity-cli")
35
+ ANTIGRAVITY_ALTERNATE_COMMANDS = ("antigravity",)
36
+ ANTIGRAVITY_AMBIENT_HOME_ENV = "ANTIGRAVITY_CLI_USE_AMBIENT_HOME"
37
+
38
+ AntigravityCliHeadChangedError = gemini_cli_audit_pr.GeminiCliHeadChangedError
39
+ AntigravityCliUnsupportedError = gemini_cli_audit_pr.GeminiCliUnsupportedError
40
+
41
+
42
+ def resolve_antigravity_api_key() -> str:
43
+ """Antigravity CLI 1.0.7 uses local OAuth; Gemini API keys are not auth."""
44
+
45
+ return ""
46
+
47
+
48
+ def _env_flag_enabled(name: str) -> bool:
49
+ return os.environ.get(name, "").strip().lower() in {"1", "true", "yes", "on"}
50
+
51
+
52
+ def resolve_antigravity_command(command: str) -> str:
53
+ """Resolve the configured Antigravity command, honoring documented aliases."""
54
+
55
+ if shutil.which(command):
56
+ return command
57
+ if command == DEFAULT_ANTIGRAVITY_COMMAND:
58
+ for alternate in ANTIGRAVITY_ALTERNATE_COMMANDS:
59
+ if shutil.which(alternate):
60
+ return alternate
61
+ return command
62
+
63
+
64
+ def run_antigravity_cli_audit(
65
+ *,
66
+ repo: str,
67
+ pr_number: int,
68
+ github_token: str,
69
+ command: str = DEFAULT_ANTIGRAVITY_COMMAND,
70
+ expected_head_sha: str | None = None,
71
+ prompt_lenses: tuple[str, ...] = code_mower_prompts.DEFAULT_REVIEW_LENSES,
72
+ prompt_dir: Path | None = None,
73
+ max_diff_bytes: int = gemini_cli_audit_pr.DEFAULT_MAX_DIFF_BYTES,
74
+ timeout_seconds: int = gemini_cli_audit_pr.DEFAULT_TIMEOUT_SECONDS,
75
+ output_dir: Path | None = None,
76
+ antigravity_api_key: str | None = None,
77
+ repo_path: Path | None = None,
78
+ base_ref: str = gemini_cli_audit_pr.DEFAULT_BASE_REF,
79
+ allow_historical_head: bool = False,
80
+ historical_calibration: bool = False,
81
+ allow_ambient_home: bool = False,
82
+ context_pack_text: str = "",
83
+ ) -> dict[str, object]:
84
+ if antigravity_api_key:
85
+ raise ValueError(
86
+ "Antigravity CLI does not currently support Gemini API keys as "
87
+ "noninteractive auth. Use local OAuth with "
88
+ f"{ANTIGRAVITY_AMBIENT_HOME_ENV}=1 in trusted environments."
89
+ )
90
+ use_ambient_home = allow_ambient_home
91
+ if not use_ambient_home:
92
+ raise ValueError(
93
+ "Antigravity CLI local OAuth auth requires explicit ambient-home "
94
+ f"opt-in. Set {ANTIGRAVITY_AMBIENT_HOME_ENV}=1 only in a trusted "
95
+ "local environment, or use a future Antigravity noninteractive auth "
96
+ "mode when available."
97
+ )
98
+ return gemini_cli_audit_pr.run_gemini_cli_audit(
99
+ repo=repo,
100
+ pr_number=pr_number,
101
+ github_token=github_token,
102
+ command=resolve_antigravity_command(command),
103
+ expected_head_sha=expected_head_sha,
104
+ prompt_lenses=prompt_lenses,
105
+ prompt_dir=prompt_dir,
106
+ max_diff_bytes=max_diff_bytes,
107
+ timeout_seconds=timeout_seconds,
108
+ output_dir=output_dir,
109
+ gemini_api_key=None,
110
+ repo_path=repo_path,
111
+ base_ref=base_ref,
112
+ allow_historical_head=allow_historical_head,
113
+ historical_calibration=historical_calibration,
114
+ mode=DEFAULT_ANTIGRAVITY_MODE,
115
+ output_stem=DEFAULT_ANTIGRAVITY_OUTPUT_STEM,
116
+ display_name=DEFAULT_ANTIGRAVITY_DISPLAY_NAME,
117
+ settings_subdirs=() if use_ambient_home else ANTIGRAVITY_SETTINGS_SUBDIRS,
118
+ model_env="ANTIGRAVITY_MODEL",
119
+ child_env_exclude=("GEMINI_API_KEY", "GOOGLE_API_KEY", "GEMINI_MODEL"),
120
+ cli_transport="prompt_file",
121
+ preserve_ambient_home=use_ambient_home,
122
+ context_pack_text=context_pack_text,
123
+ )
124
+
125
+
126
+ def render_text(payload: dict[str, object]) -> str:
127
+ text = gemini_cli_audit_pr.render_text(payload)
128
+ return text.replace("Gemini CLI audit", "Antigravity CLI audit", 1)
129
+
130
+
131
+ def main(argv: list[str] | None = None) -> int:
132
+ parser = argparse.ArgumentParser(description=__doc__)
133
+ parser.add_argument("--repo", required=True, help="owner/repo")
134
+ parser.add_argument("--pr", type=int, required=True, help="PR number")
135
+ parser.add_argument("--expected-head-sha", default=None)
136
+ parser.add_argument(
137
+ "--repo-path",
138
+ type=Path,
139
+ default=None,
140
+ help="optional local checkout to diff for archived calibration heads",
141
+ )
142
+ parser.add_argument("--base-ref", default=gemini_cli_audit_pr.DEFAULT_BASE_REF)
143
+ parser.add_argument(
144
+ "--allow-historical-head",
145
+ action="store_true",
146
+ help="allow --repo-path HEAD to differ from the current GitHub PR head",
147
+ )
148
+ parser.add_argument(
149
+ "--historical-calibration",
150
+ action="store_true",
151
+ help=(
152
+ "mark this as non-merge-authority evidence against an archived PR "
153
+ "head; implies --allow-historical-head for local checkouts"
154
+ ),
155
+ )
156
+ parser.add_argument(
157
+ "--command",
158
+ default=os.environ.get("ANTIGRAVITY_CLI_COMMAND", DEFAULT_ANTIGRAVITY_COMMAND),
159
+ )
160
+ parser.add_argument(
161
+ "--prompt-lenses",
162
+ default=",".join(code_mower_prompts.DEFAULT_REVIEW_LENSES),
163
+ )
164
+ parser.add_argument("--prompt-dir", type=Path, default=None)
165
+ parser.add_argument(
166
+ "--context-pack-file",
167
+ action="append",
168
+ type=Path,
169
+ default=[],
170
+ help="Bounded context-pack text file to append to the audit prompt.",
171
+ )
172
+ parser.add_argument(
173
+ "--max-diff-bytes",
174
+ type=int,
175
+ default=gemini_cli_audit_pr.DEFAULT_MAX_DIFF_BYTES,
176
+ )
177
+ parser.add_argument(
178
+ "--timeout",
179
+ type=int,
180
+ default=gemini_cli_audit_pr.DEFAULT_TIMEOUT_SECONDS,
181
+ )
182
+ parser.add_argument("--output-dir", type=Path, default=None)
183
+ parser.add_argument("--json", action="store_true")
184
+ args = parser.parse_args(argv)
185
+
186
+ token = gemini_cli_audit_pr.resolve_github_token()
187
+ if not token:
188
+ print(
189
+ "error: set GITHUB_TOKEN or authenticate gh so `gh auth token` works",
190
+ file=sys.stderr,
191
+ )
192
+ return 1
193
+ antigravity_api_key = resolve_antigravity_api_key() or None
194
+ try:
195
+ context_pack_text = "\n\n".join(
196
+ path.read_text(encoding="utf-8") for path in args.context_pack_file
197
+ )
198
+ payload = run_antigravity_cli_audit(
199
+ repo=args.repo,
200
+ pr_number=args.pr,
201
+ github_token=token,
202
+ command=args.command,
203
+ expected_head_sha=args.expected_head_sha,
204
+ prompt_lenses=code_mower_prompts.split_lenses(args.prompt_lenses),
205
+ prompt_dir=args.prompt_dir,
206
+ max_diff_bytes=args.max_diff_bytes,
207
+ timeout_seconds=args.timeout,
208
+ output_dir=args.output_dir,
209
+ antigravity_api_key=antigravity_api_key,
210
+ repo_path=args.repo_path,
211
+ base_ref=args.base_ref,
212
+ allow_historical_head=args.allow_historical_head,
213
+ historical_calibration=args.historical_calibration,
214
+ allow_ambient_home=_env_flag_enabled(ANTIGRAVITY_AMBIENT_HOME_ENV),
215
+ context_pack_text=context_pack_text,
216
+ )
217
+ except AntigravityCliHeadChangedError as exc:
218
+ print(f"error: {exc}", file=sys.stderr)
219
+ return 2
220
+ except (
221
+ AntigravityCliUnsupportedError,
222
+ OSError,
223
+ ValueError,
224
+ subprocess.CalledProcessError,
225
+ subprocess.TimeoutExpired,
226
+ urllib.error.URLError,
227
+ ) as exc:
228
+ print(f"error: {exc}", file=sys.stderr)
229
+ return 1
230
+
231
+ if args.json:
232
+ print(json.dumps(payload, indent=2, sort_keys=True))
233
+ else:
234
+ print(render_text(payload), end="")
235
+ if payload.get("returncode") != 0:
236
+ return 1
237
+ return 0 if gemini_cli_audit_pr._verdict_is_usable(payload.get("verdict")) else 1
238
+
239
+
240
+ if __name__ == "__main__":
241
+ raise SystemExit(main())