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