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,469 @@
1
+ """Reference Code Mower provider registry.
2
+
3
+ This module is intentionally declarative. It documents the provider/lane shape
4
+ that a future `code-mower.yml` parser should produce without making the shared
5
+ labelers know provider-specific semantics.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+ from types import MappingProxyType
12
+ from typing import Any, Mapping
13
+
14
+ try:
15
+ from . import local_llm_profiles
16
+ except ImportError: # pragma: no cover - direct repo execution fallback.
17
+ from tools import local_llm_profiles
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class LaneLabels:
22
+ needs: str
23
+ done: str
24
+ blocked: str
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class ProviderLane:
29
+ lane_id: str
30
+ lane_type: str
31
+ driver: str
32
+ provider: str
33
+ labels: LaneLabels
34
+ adapter: str | None = None
35
+ events: tuple[str, ...] = ()
36
+ token_env: tuple[str, ...] = ()
37
+ token_env_any: tuple[tuple[str, ...], ...] = ()
38
+ result_sources: tuple[str, ...] = ()
39
+ informational: bool = False
40
+ merge_authority: bool = False
41
+ enabled_by_default: bool = True
42
+ trigger_policy: str = "label"
43
+ spend_policy: str = "none"
44
+ provider_config: Mapping[str, Any] = field(default_factory=dict)
45
+
46
+ def __post_init__(self) -> None:
47
+ object.__setattr__(self, "provider_config", _freeze_mapping(self.provider_config))
48
+
49
+ @property
50
+ def is_paid_optional(self) -> bool:
51
+ return (
52
+ self.spend_policy == "paid"
53
+ and not self.enabled_by_default
54
+ and self.trigger_policy == "manual"
55
+ )
56
+
57
+
58
+ def _freeze_value(value: Any) -> Any:
59
+ if isinstance(value, Mapping):
60
+ return _freeze_mapping(value)
61
+ if isinstance(value, (list, tuple)):
62
+ return tuple(_freeze_value(item) for item in value)
63
+ if isinstance(value, set):
64
+ return frozenset(_freeze_value(item) for item in value)
65
+ return value
66
+
67
+
68
+ def _freeze_mapping(value: Mapping[str, Any]) -> Mapping[str, Any]:
69
+ return MappingProxyType({key: _freeze_value(item) for key, item in value.items()})
70
+
71
+
72
+ REFERENCE_PROVIDERS: dict[str, ProviderLane] = {
73
+ "codex": ProviderLane(
74
+ lane_id="codex",
75
+ lane_type="audit",
76
+ driver="local_cli",
77
+ provider="codex",
78
+ labels=LaneLabels(
79
+ needs="needs-codex-audit",
80
+ done="codex-audit-done",
81
+ blocked="codex-audit-blocked",
82
+ ),
83
+ token_env=("CODEX_AUDIT_LABEL_TOKEN", "GITHUB_TOKEN"),
84
+ result_sources=("trailer_comment",),
85
+ merge_authority=True,
86
+ provider_config={
87
+ "command": "codex",
88
+ "doctor_probe_args": ("--version",),
89
+ },
90
+ ),
91
+ "claude_review": ProviderLane(
92
+ lane_id="claude_review",
93
+ lane_type="review",
94
+ driver="manual",
95
+ provider="claude",
96
+ labels=LaneLabels(
97
+ needs="needs-claude-review",
98
+ done="claude-review-done",
99
+ blocked="claude-review-blocked",
100
+ ),
101
+ result_sources=("post_review",),
102
+ informational=True,
103
+ merge_authority=False,
104
+ trigger_policy="manual",
105
+ ),
106
+ "claude_audit": ProviderLane(
107
+ lane_id="claude_audit",
108
+ lane_type="audit",
109
+ driver="local_cli",
110
+ provider="claude",
111
+ labels=LaneLabels(
112
+ needs="needs-claude-audit",
113
+ done="claude-audit-done",
114
+ blocked="claude-audit-blocked",
115
+ ),
116
+ token_env=("CLAUDE_AUDIT_LABEL_TOKEN", "GITHUB_TOKEN"),
117
+ result_sources=("trailer_comment",),
118
+ merge_authority=True,
119
+ provider_config={
120
+ "command": "claude",
121
+ "doctor_probe_args": (
122
+ "--print",
123
+ "--output-format",
124
+ "json",
125
+ "--no-session-persistence",
126
+ "--setting-sources",
127
+ "local",
128
+ "--strict-mcp-config",
129
+ "--mcp-config",
130
+ '{"mcpServers":{}}',
131
+ "--disable-slash-commands",
132
+ "--tools",
133
+ "",
134
+ "--model",
135
+ "sonnet",
136
+ "--max-budget-usd",
137
+ "0.25",
138
+ "Reply with exactly: ok",
139
+ ),
140
+ "doctor_probe_timeout_seconds": 30,
141
+ "doctor_probe_expect_json": True,
142
+ "doctor_probe_expect_json_field": "result",
143
+ "doctor_probe_expect_json_value": "ok",
144
+ "doctor_probe_error_fields": ("is_error", "api_error_status"),
145
+ "doctor_probe_auth_status_fields": ("api_error_status",),
146
+ },
147
+ ),
148
+ "devin": ProviderLane(
149
+ lane_id="devin",
150
+ lane_type="audit",
151
+ driver="hosted_bridge",
152
+ provider="devin",
153
+ labels=LaneLabels(
154
+ needs="needs-devin-audit",
155
+ done="devin-audit-done",
156
+ blocked="devin-audit-blocked",
157
+ ),
158
+ token_env=("DEVIN_AUDIT_LABEL_TOKEN", "GITHUB_TOKEN"),
159
+ result_sources=("trailer_comment",),
160
+ merge_authority=True,
161
+ enabled_by_default=False,
162
+ trigger_policy="manual",
163
+ spend_policy="paid",
164
+ ),
165
+ "greptile": ProviderLane(
166
+ lane_id="greptile",
167
+ lane_type="audit",
168
+ driver="saas_event",
169
+ provider="greptile",
170
+ adapter="greptile",
171
+ events=("pull_request_review", "check_run"),
172
+ labels=LaneLabels(
173
+ needs="needs-greptile-audit",
174
+ done="greptile-audit-done",
175
+ blocked="greptile-audit-blocked",
176
+ ),
177
+ token_env=("GREPTILE_AUDIT_LABEL_TOKEN", "GITHUB_TOKEN"),
178
+ result_sources=("pull_request_review", "check_run"),
179
+ informational=True,
180
+ provider_config={
181
+ "bot_authors": ("greptile-apps[bot]", "greptile-apps"),
182
+ "check_runs": ({"app_slug": "greptile-apps", "name": "Greptile Review"},),
183
+ },
184
+ ),
185
+ "gitar": ProviderLane(
186
+ lane_id="gitar",
187
+ lane_type="audit",
188
+ driver="saas_event",
189
+ provider="gitar",
190
+ adapter="gitar",
191
+ events=("issue_comment",),
192
+ labels=LaneLabels(
193
+ needs="needs-gitar-audit",
194
+ done="gitar-audit-done",
195
+ blocked="gitar-audit-blocked",
196
+ ),
197
+ token_env=("GITAR_AUDIT_LABEL_TOKEN", "GITHUB_TOKEN"),
198
+ result_sources=("issue_comment",),
199
+ informational=True,
200
+ provider_config={
201
+ "bot_authors": ("gitar-ai[bot]", "gitar-bot", "gitar-bot[bot]"),
202
+ },
203
+ ),
204
+ "qodo": ProviderLane(
205
+ lane_id="qodo",
206
+ lane_type="audit",
207
+ driver="saas_event",
208
+ provider="qodo",
209
+ adapter="qodo",
210
+ events=("issue_comment",),
211
+ labels=LaneLabels(
212
+ needs="needs-qodo-audit",
213
+ done="qodo-audit-done",
214
+ blocked="qodo-audit-blocked",
215
+ ),
216
+ token_env=("QODO_AUDIT_LABEL_TOKEN", "GITHUB_TOKEN"),
217
+ result_sources=("issue_comment",),
218
+ informational=True,
219
+ enabled_by_default=False,
220
+ trigger_policy="manual",
221
+ spend_policy="paid",
222
+ provider_config={
223
+ "bot_authors": ("qodo-code-review[bot]", "qodo-code-review"),
224
+ "spend_policy": "never trigger automatically from the reference workflows",
225
+ },
226
+ ),
227
+ "cursor_bugbot": ProviderLane(
228
+ lane_id="cursor_bugbot",
229
+ lane_type="audit",
230
+ driver="saas_event",
231
+ provider="cursor_bugbot",
232
+ adapter="cursor_bugbot",
233
+ events=("issue_comment",),
234
+ labels=LaneLabels(
235
+ needs="needs-cursor-bugbot-audit",
236
+ done="cursor-bugbot-audit-done",
237
+ blocked="cursor-bugbot-audit-blocked",
238
+ ),
239
+ token_env=("CURSOR_BUGBOT_AUDIT_LABEL_TOKEN", "GITHUB_TOKEN"),
240
+ result_sources=("issue_comment",),
241
+ informational=True,
242
+ enabled_by_default=False,
243
+ trigger_policy="manual",
244
+ spend_policy="paid",
245
+ provider_config={
246
+ "bot_authors": ("cursor[bot]", "cursor"),
247
+ "trigger_comments": ("bugbot run", "@cursor review"),
248
+ "rules_file": ".cursor/BUGBOT.md",
249
+ "status": (
250
+ "manual informational lane; keep calibration-only until enabled "
251
+ "BugBot output shape is captured and adjudicated"
252
+ ),
253
+ },
254
+ ),
255
+ "local_llm": ProviderLane(
256
+ lane_id="local_llm",
257
+ lane_type="audit",
258
+ driver="api_model",
259
+ provider="openai_compatible",
260
+ labels=LaneLabels(
261
+ needs="needs-local-llm-audit",
262
+ done="local-llm-audit-done",
263
+ blocked="local-llm-audit-blocked",
264
+ ),
265
+ token_env=("LOCAL_LLM_AUDIT_LABEL_TOKEN", "LOCAL_LLM_API_KEY", "GITHUB_TOKEN"),
266
+ result_sources=("trailer_comment",),
267
+ informational=True,
268
+ enabled_by_default=False,
269
+ trigger_policy="manual",
270
+ spend_policy="local",
271
+ provider_config={
272
+ "api_base_env": "LOCAL_LLM_API_BASE",
273
+ "model_env": "LOCAL_LLM_MODEL",
274
+ "bot_authors_env": "LOCAL_LLM_BOT_AUTHORS",
275
+ "default_api_base": "http://localhost:1234/v1",
276
+ "profile_env": "LOCAL_LLM_PROFILE",
277
+ # Keep provider metadata synced with the canonical profile registry.
278
+ "profiles": local_llm_profiles.profile_ids(),
279
+ "status": "informational calibration lane, not merge authority",
280
+ },
281
+ ),
282
+ "aider": ProviderLane(
283
+ lane_id="aider",
284
+ lane_type="audit",
285
+ driver="local_cli",
286
+ provider="aider",
287
+ labels=LaneLabels(
288
+ needs="needs-aider-audit",
289
+ done="aider-audit-done",
290
+ blocked="aider-audit-blocked",
291
+ ),
292
+ token_env=("AIDER_AUDIT_LABEL_TOKEN", "GITHUB_TOKEN"),
293
+ result_sources=("trailer_comment",),
294
+ informational=True,
295
+ enabled_by_default=False,
296
+ trigger_policy="manual",
297
+ spend_policy="local",
298
+ provider_config={
299
+ "command": "aider",
300
+ "mode": "review-only",
301
+ "model_env": "AIDER_MODEL",
302
+ "api_base_env": "AIDER_API_BASE",
303
+ "status": "optional tool lane; provider-specific invocation belongs in an adapter",
304
+ },
305
+ ),
306
+ "gemini_cli": ProviderLane(
307
+ lane_id="gemini_cli",
308
+ lane_type="audit",
309
+ driver="local_cli",
310
+ provider="gemini",
311
+ labels=LaneLabels(
312
+ needs="needs-gemini-cli-audit",
313
+ done="gemini-cli-audit-done",
314
+ blocked="gemini-cli-audit-blocked",
315
+ ),
316
+ token_env=("GITHUB_TOKEN",),
317
+ token_env_any=(("GEMINI_API_KEY", "GOOGLE_API_KEY"),),
318
+ result_sources=("trailer_comment",),
319
+ informational=True,
320
+ enabled_by_default=False,
321
+ trigger_policy="manual",
322
+ spend_policy="included",
323
+ provider_config={
324
+ "command": "gemini",
325
+ "command_env": "GEMINI_CLI_COMMAND",
326
+ "model_env": "GEMINI_MODEL",
327
+ "prompt_lenses": ("base-audit",),
328
+ "doctor_probe_args": (
329
+ "-p",
330
+ "Reply with exactly: ok",
331
+ "--output-format",
332
+ "json",
333
+ "--skip-trust",
334
+ ),
335
+ "doctor_probe_timeout_seconds": 45,
336
+ "doctor_probe_expect_json": True,
337
+ "doctor_probe_expect_json_field": "response",
338
+ "doctor_probe_expect_json_value": "ok",
339
+ "status": "optional third-peer CLI lane; not merge authority until calibrated",
340
+ },
341
+ ),
342
+ "antigravity_cli": ProviderLane(
343
+ lane_id="antigravity_cli",
344
+ lane_type="audit",
345
+ driver="local_cli",
346
+ provider="antigravity",
347
+ labels=LaneLabels(
348
+ needs="needs-antigravity-cli-audit",
349
+ done="antigravity-cli-audit-done",
350
+ blocked="antigravity-cli-audit-blocked",
351
+ ),
352
+ token_env=("GITHUB_TOKEN",),
353
+ result_sources=("trailer_comment",),
354
+ informational=True,
355
+ enabled_by_default=False,
356
+ trigger_policy="manual",
357
+ spend_policy="included",
358
+ provider_config={
359
+ "command": "agy",
360
+ "alternate_commands": ("antigravity",),
361
+ "command_env": "ANTIGRAVITY_CLI_COMMAND",
362
+ "model_env": "ANTIGRAVITY_MODEL",
363
+ "prompt_lenses": ("base-audit",),
364
+ "required_env_truthy": ("ANTIGRAVITY_CLI_USE_AMBIENT_HOME",),
365
+ "doctor_probe_args": ("--version",),
366
+ "auth": (
367
+ "run `agy` login/install locally; set "
368
+ "ANTIGRAVITY_CLI_USE_AMBIENT_HOME=1 to opt into local OAuth "
369
+ "state in trusted environments"
370
+ ),
371
+ "status": (
372
+ "forward Google CLI research lane; not merge authority until "
373
+ "calibrated against Gemini compatibility records"
374
+ ),
375
+ },
376
+ ),
377
+ "hermes_cli": ProviderLane(
378
+ lane_id="hermes_cli",
379
+ lane_type="audit",
380
+ driver="local_cli",
381
+ provider="hermes",
382
+ labels=LaneLabels(
383
+ needs="needs-hermes-cli-audit",
384
+ done="hermes-cli-audit-done",
385
+ blocked="hermes-cli-audit-blocked",
386
+ ),
387
+ token_env=("GITHUB_TOKEN",),
388
+ result_sources=("trailer_comment",),
389
+ informational=True,
390
+ enabled_by_default=False,
391
+ trigger_policy="manual",
392
+ spend_policy="included",
393
+ provider_config={
394
+ "command": "hermes",
395
+ "command_env": "HERMES_CLI_COMMAND",
396
+ "model_env": "HERMES_INFERENCE_MODEL",
397
+ "provider_env": "HERMES_PROVIDER",
398
+ "prompt_lenses": ("base-audit",),
399
+ "required_env_truthy": ("HERMES_CLI_USE_AMBIENT_HOME",),
400
+ "doctor_probe_args": ("--version",),
401
+ "auth": (
402
+ "run `hermes setup` or provider login locally; set "
403
+ "HERMES_CLI_USE_AMBIENT_HOME=1 to opt into local Hermes "
404
+ "auth/session state in trusted environments"
405
+ ),
406
+ "status": (
407
+ "informational Hermes Agent one-shot lane; not merge authority "
408
+ "until calibrated and kept stateless enough for blind review"
409
+ ),
410
+ },
411
+ ),
412
+ "coderabbit_cli": ProviderLane(
413
+ lane_id="coderabbit_cli",
414
+ lane_type="audit",
415
+ driver="local_cli",
416
+ provider="coderabbit",
417
+ labels=LaneLabels(
418
+ needs="needs-coderabbit-cli-audit",
419
+ done="coderabbit-cli-audit-done",
420
+ blocked="coderabbit-cli-audit-blocked",
421
+ ),
422
+ token_env=("GITHUB_TOKEN",),
423
+ result_sources=("trailer_comment",),
424
+ informational=True,
425
+ enabled_by_default=False,
426
+ trigger_policy="manual",
427
+ spend_policy="included",
428
+ provider_config={
429
+ "command": "coderabbit",
430
+ "alternate_commands": ("cr",),
431
+ "status": "optional CLI lane for local/on-demand review capture",
432
+ },
433
+ ),
434
+ "acp_bridge": ProviderLane(
435
+ lane_id="acp_bridge",
436
+ lane_type="audit",
437
+ driver="local_cli",
438
+ provider="acp",
439
+ labels=LaneLabels(
440
+ needs="needs-acp-audit",
441
+ done="acp-audit-done",
442
+ blocked="acp-audit-blocked",
443
+ ),
444
+ token_env=("GITHUB_TOKEN",),
445
+ result_sources=("trailer_comment",),
446
+ informational=True,
447
+ enabled_by_default=False,
448
+ trigger_policy="manual",
449
+ spend_policy="none",
450
+ provider_config={
451
+ "command_env": "CODE_MOWER_ACP_COMMAND",
452
+ "protocol": "agent-client-protocol",
453
+ "prompt_lenses": ("base-audit",),
454
+ "status": "protocol research lane; no automatic merge authority",
455
+ },
456
+ ),
457
+ }
458
+
459
+
460
+ def get_provider(lane_id: str) -> ProviderLane:
461
+ return REFERENCE_PROVIDERS[lane_id]
462
+
463
+
464
+ def audit_queue_labels() -> tuple[str, ...]:
465
+ return tuple(
466
+ lane.labels.needs
467
+ for lane in REFERENCE_PROVIDERS.values()
468
+ if lane.lane_type in {"audit", "review"}
469
+ )
@@ -0,0 +1,60 @@
1
+ """Shared provider-runner primitives.
2
+
3
+ Provider-specific modules remain responsible for prompts and verdict parsing.
4
+ This package holds common process/auth helpers used by Codex, Claude,
5
+ Gemini/Antigravity, Hermes, and local reviewer lanes.
6
+ """
7
+
8
+ from .comments import MAX_GITHUB_COMMENT_CHARS, limit_comment_body
9
+ from .git import fetch_local_checkout_diff, local_head_sha, run_git
10
+ from .github_auth import (
11
+ pop_github_token_env,
12
+ resolve_github_token_from_env_or_gh,
13
+ resolve_github_token_from_stdin_or_env,
14
+ )
15
+ from .github_pr import (
16
+ fetch_pull_request,
17
+ fetch_pull_request_diff,
18
+ fetch_pull_request_files,
19
+ post_pr_comment,
20
+ )
21
+ from .process import DEFAULT_HOME_ENV_KEYS, build_allowlisted_child_env
22
+ from .repo_paths import parse_repo_paths
23
+ from .text_schema import clip_text, one_line, require_exact_keys
24
+ from .verdict_artifacts import (
25
+ load_audit_verdict_artifact,
26
+ repost_audit_verdict_artifact,
27
+ write_audit_verdict_artifact,
28
+ )
29
+ from .workspace import (
30
+ ProviderWorkspaceError,
31
+ verify_checkout_at_head,
32
+ working_tree_status,
33
+ )
34
+
35
+ __all__ = [
36
+ "fetch_pull_request",
37
+ "fetch_pull_request_diff",
38
+ "fetch_pull_request_files",
39
+ "fetch_local_checkout_diff",
40
+ "load_audit_verdict_artifact",
41
+ "local_head_sha",
42
+ "limit_comment_body",
43
+ "MAX_GITHUB_COMMENT_CHARS",
44
+ "clip_text",
45
+ "DEFAULT_HOME_ENV_KEYS",
46
+ "one_line",
47
+ "parse_repo_paths",
48
+ "pop_github_token_env",
49
+ "post_pr_comment",
50
+ "ProviderWorkspaceError",
51
+ "repost_audit_verdict_artifact",
52
+ "require_exact_keys",
53
+ "resolve_github_token_from_env_or_gh",
54
+ "resolve_github_token_from_stdin_or_env",
55
+ "run_git",
56
+ "verify_checkout_at_head",
57
+ "working_tree_status",
58
+ "build_allowlisted_child_env",
59
+ "write_audit_verdict_artifact",
60
+ ]
@@ -0,0 +1,31 @@
1
+ """Shared GitHub comment formatting helpers for provider wrappers."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ MAX_GITHUB_COMMENT_CHARS = 64_000
7
+
8
+
9
+ def limit_comment_body(
10
+ body: str,
11
+ trailer: str,
12
+ *,
13
+ provider_name: str,
14
+ max_chars: int = MAX_GITHUB_COMMENT_CHARS,
15
+ ) -> str:
16
+ """Keep an audit comment under GitHub's body-size limit without losing trailer state."""
17
+
18
+ if len(body) <= max_chars:
19
+ return body
20
+
21
+ note = (
22
+ f"\n\n[{provider_name} audit comment truncated to stay under "
23
+ "GitHub's comment-size limit.]\n\n"
24
+ )
25
+ suffix = note + trailer + "\n"
26
+ allowed_prefix_len = max_chars - len(suffix)
27
+ if allowed_prefix_len < 0:
28
+ return suffix[-max_chars:]
29
+
30
+ prefix = body.rsplit(trailer, 1)[0] if trailer in body else body
31
+ return prefix[:allowed_prefix_len].rstrip() + suffix
@@ -0,0 +1,46 @@
1
+ """Shared git helpers for provider-runner wrappers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ from pathlib import Path
7
+ from typing import Sequence
8
+
9
+
10
+ def run_git(
11
+ repo_path: Path,
12
+ args: Sequence[str],
13
+ *,
14
+ check: bool = True,
15
+ timeout: int = 30,
16
+ ) -> subprocess.CompletedProcess[str]:
17
+ """Run ``git -C repo_path`` and return captured text output."""
18
+
19
+ return subprocess.run(
20
+ ["git", "-C", str(repo_path), *args],
21
+ capture_output=True,
22
+ text=True,
23
+ check=check,
24
+ timeout=timeout,
25
+ )
26
+
27
+
28
+ def local_head_sha(repo_path: Path) -> str:
29
+ """Return the current HEAD SHA for a local git checkout."""
30
+
31
+ return run_git(repo_path, ["rev-parse", "HEAD"]).stdout.strip()
32
+
33
+
34
+ def fetch_local_checkout_diff(repo_path: Path, *, base_ref: str) -> tuple[str, str]:
35
+ """Return ``(head_sha, diff)`` for a local checkout against ``base_ref``."""
36
+
37
+ resolved_repo_path = repo_path.expanduser().resolve()
38
+ if not resolved_repo_path.is_dir():
39
+ raise ValueError(f"repo path does not exist: {resolved_repo_path}")
40
+ head_sha = local_head_sha(resolved_repo_path)
41
+ diff = run_git(
42
+ resolved_repo_path,
43
+ ["diff", "--no-ext-diff", "--find-renames", f"{base_ref}...HEAD"],
44
+ timeout=120,
45
+ ).stdout
46
+ return head_sha, diff
@@ -0,0 +1,61 @@
1
+ """GitHub token resolution helpers for provider runner CLIs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import subprocess
7
+ import sys
8
+ from typing import TextIO
9
+
10
+
11
+ def pop_github_token_env() -> str | None:
12
+ """Return GITHUB_TOKEN and clear GitHub token aliases from this process.
13
+
14
+ This preserves the legacy direct-invocation behavior for provider wrappers
15
+ while keeping the cleanup logic in one place.
16
+ """
17
+
18
+ token = os.environ.pop("GITHUB_TOKEN", None)
19
+ os.environ.pop("GH_TOKEN", None)
20
+ return token
21
+
22
+
23
+ def resolve_github_token_from_stdin_or_env(
24
+ read_from_stdin: bool,
25
+ *,
26
+ stdin: TextIO | None = None,
27
+ ) -> str | None:
28
+ """Resolve a GitHub token from stdin or the legacy process environment."""
29
+
30
+ if read_from_stdin:
31
+ source = stdin if stdin is not None else sys.stdin
32
+ line = source.readline()
33
+ os.environ.pop("GITHUB_TOKEN", None)
34
+ os.environ.pop("GH_TOKEN", None)
35
+ if not line:
36
+ return None
37
+ return line.rstrip("\r\n")
38
+ return pop_github_token_env()
39
+
40
+
41
+ def resolve_github_token_from_env_or_gh() -> str:
42
+ """Resolve a GitHub token from env or `gh auth token` for local lanes."""
43
+
44
+ token = os.environ.get("GITHUB_TOKEN")
45
+ if token:
46
+ return token
47
+ try:
48
+ completed = subprocess.run(
49
+ ["gh", "auth", "token"],
50
+ check=True,
51
+ capture_output=True,
52
+ text=True,
53
+ timeout=10,
54
+ )
55
+ except (
56
+ FileNotFoundError,
57
+ subprocess.CalledProcessError,
58
+ subprocess.TimeoutExpired,
59
+ ):
60
+ return ""
61
+ return completed.stdout.strip()