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,718 @@
1
+ #!/usr/bin/env python3
2
+ """Package-install rehearsal helpers for Code Mower migration gates."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import os
7
+ import shutil
8
+ import sys
9
+ import tempfile
10
+ from pathlib import Path
11
+ from typing import Any, Sequence
12
+
13
+ if __package__ in {None, ""}:
14
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
15
+ from code_mower.migration_install import (
16
+ MIRRORED_IMPLEMENTATION_PATTERNS,
17
+ CommandResult,
18
+ RehearsalError,
19
+ RunOutput,
20
+ _default_product_rehearsal_local_command,
21
+ _glob_relative_files,
22
+ _json_payload,
23
+ _load_release_readiness,
24
+ _pip_install_command,
25
+ _resolve_install_package_spec,
26
+ _run,
27
+ _run_rehearsal_step,
28
+ _run_rehearsal_step_to_file,
29
+ _venv_code_mower,
30
+ _venv_python,
31
+ _write_json,
32
+ _write_public_rehearsal_toy_repo,
33
+ )
34
+ from code_mower.migration_readiness import (
35
+ FIRST_USER_ARTIFACTS as FIRST_USER_ARTIFACTS,
36
+ PRIVACY_EXCLUDED_CONTENT as PRIVACY_EXCLUDED_CONTENT,
37
+ first_user_artifacts as _first_user_artifacts,
38
+ first_user_readiness_scorecard as _first_user_readiness_scorecard,
39
+ )
40
+ else:
41
+ from .migration_install import (
42
+ MIRRORED_IMPLEMENTATION_PATTERNS,
43
+ CommandResult,
44
+ RehearsalError,
45
+ RunOutput,
46
+ _default_product_rehearsal_local_command,
47
+ _glob_relative_files,
48
+ _json_payload,
49
+ _load_release_readiness,
50
+ _pip_install_command,
51
+ _resolve_install_package_spec,
52
+ _run,
53
+ _run_rehearsal_step,
54
+ _run_rehearsal_step_to_file,
55
+ _venv_code_mower,
56
+ _venv_python,
57
+ _write_json,
58
+ _write_public_rehearsal_toy_repo,
59
+ )
60
+ from .migration_readiness import (
61
+ FIRST_USER_ARTIFACTS as FIRST_USER_ARTIFACTS,
62
+ PRIVACY_EXCLUDED_CONTENT as PRIVACY_EXCLUDED_CONTENT,
63
+ first_user_artifacts as _first_user_artifacts,
64
+ first_user_readiness_scorecard as _first_user_readiness_scorecard,
65
+ )
66
+
67
+ __all__ = [
68
+ "FIRST_USER_ARTIFACTS",
69
+ "MIRRORED_IMPLEMENTATION_PATTERNS",
70
+ "PRIVACY_EXCLUDED_CONTENT",
71
+ "CommandResult",
72
+ "RehearsalError",
73
+ "RunOutput",
74
+ "_default_product_rehearsal_local_command",
75
+ "_first_user_artifacts",
76
+ "_first_user_readiness_scorecard",
77
+ "_glob_relative_files",
78
+ "_json_payload",
79
+ "_load_release_readiness",
80
+ "_pip_install_command",
81
+ "_resolve_python_executable",
82
+ "_resolve_install_package_spec",
83
+ "_run",
84
+ "_run_rehearsal_step",
85
+ "_run_rehearsal_step_to_file",
86
+ "_write_json",
87
+ "_write_public_rehearsal_toy_repo",
88
+ "_write_rehearsal_auto_discovery_fixture",
89
+ "render_package_install_rehearsal_text",
90
+ "run_package_install_rehearsal",
91
+ ]
92
+
93
+
94
+ def _repo_has_product_wrapper(repo_path: Path) -> bool:
95
+ return (repo_path / "tools" / "code_mower").is_file()
96
+
97
+
98
+ def _run_external_repo_readiness(
99
+ *,
100
+ code_mower_bin: Path,
101
+ repo_path: Path,
102
+ env: dict[str, str],
103
+ steps: list[dict[str, Any]],
104
+ timeout: int,
105
+ ) -> dict[str, Any]:
106
+ """Validate installed CLI behavior against a repo without local wrappers."""
107
+
108
+ detect_completed = _run_rehearsal_step(
109
+ [
110
+ str(code_mower_bin),
111
+ "checks",
112
+ "detect",
113
+ "--repo-path",
114
+ str(repo_path),
115
+ "--json",
116
+ ],
117
+ cwd=repo_path,
118
+ env=env,
119
+ steps=steps,
120
+ timeout=timeout,
121
+ )
122
+ detect_payload = _json_payload(detect_completed.stdout)
123
+ dry_run_completed = _run_rehearsal_step(
124
+ [
125
+ str(code_mower_bin),
126
+ "checks",
127
+ "run",
128
+ "--repo-path",
129
+ str(repo_path),
130
+ "--dry-run",
131
+ "--json",
132
+ ],
133
+ cwd=repo_path,
134
+ env=env,
135
+ steps=steps,
136
+ timeout=timeout,
137
+ )
138
+ dry_run_payload = _json_payload(dry_run_completed.stdout)
139
+ doctor_completed = _run_rehearsal_step(
140
+ [
141
+ str(code_mower_bin),
142
+ "doctor",
143
+ "--easy",
144
+ "--json",
145
+ ],
146
+ cwd=repo_path,
147
+ env=env,
148
+ steps=steps,
149
+ timeout=timeout,
150
+ )
151
+ doctor_payload = _json_payload(doctor_completed.stdout)
152
+
153
+ check_count = 0
154
+ if isinstance(detect_payload, dict):
155
+ check_count = int(detect_payload.get("check_count") or 0)
156
+
157
+ return {
158
+ "mode": "code-mower-external-repo-readiness",
159
+ # _run_rehearsal_step raises RehearsalError on non-zero exit, so reaching
160
+ # this point means all three installed-CLI checks completed successfully.
161
+ "status": "pass",
162
+ "repo_path": str(repo_path),
163
+ "wrapper_present": False,
164
+ "check_count": check_count,
165
+ "checks_detect": detect_payload,
166
+ "checks_dry_run": dry_run_payload,
167
+ "doctor": doctor_payload,
168
+ "note": (
169
+ "No repo-local tools/code_mower wrapper was found, so the rehearsal "
170
+ "validated the installed Code Mower CLI against the repo instead of "
171
+ "running product-wrapper parity."
172
+ ),
173
+ }
174
+
175
+
176
+ def _write_rehearsal_auto_discovery_fixture(path: Path) -> None:
177
+ """Write a tiny GitHub PR-list fixture for offline auto-discovery rehearsal."""
178
+
179
+ fixed_head = "b" * 40
180
+ blocked_head = "a" * 40
181
+ path.parent.mkdir(parents=True, exist_ok=True)
182
+ _write_json(
183
+ path,
184
+ [
185
+ {
186
+ "number": 1,
187
+ "title": "Clean rehearsal docs update",
188
+ "headRefOid": "c" * 40,
189
+ "baseRefName": "main",
190
+ "changedFiles": 1,
191
+ "comments": [],
192
+ "reviews": [],
193
+ },
194
+ {
195
+ "number": 2,
196
+ "title": "Fix rehearsal blocker",
197
+ "headRefOid": fixed_head,
198
+ "baseRefName": "main",
199
+ "changedFiles": 4,
200
+ "comments": [
201
+ {
202
+ "body": (
203
+ f"Head SHA: `{blocked_head}`\n"
204
+ "Findings: P0=0, P1=0, P2=1, P3=0\n"
205
+ "<!-- CODEX_AUDIT_STATE: codex-audit-blocked -->\n"
206
+ )
207
+ },
208
+ {
209
+ "body": (
210
+ f"Head SHA: `{fixed_head}`\n"
211
+ "Findings: P0=0, P1=0, P2=0, P3=0\n"
212
+ "<!-- CODEX_AUDIT_STATE: codex-audit-done -->\n"
213
+ )
214
+ },
215
+ ],
216
+ "reviews": [],
217
+ },
218
+ ],
219
+ )
220
+
221
+
222
+ def _resolve_python_executable(python: Path | None) -> Path:
223
+ if python is None:
224
+ return Path(sys.executable)
225
+
226
+ raw = python.expanduser()
227
+ if not raw.is_absolute() and len(raw.parts) == 1:
228
+ resolved_command = shutil.which(str(raw))
229
+ if resolved_command:
230
+ return Path(resolved_command).resolve()
231
+ raise ValueError(
232
+ f"Python executable not found on PATH: {raw}. "
233
+ "Use an absolute path such as `--python \"$(command -v python3.12)\"`."
234
+ )
235
+
236
+ resolved = raw.resolve()
237
+ if not resolved.exists():
238
+ raise ValueError(f"Python executable does not exist: {resolved}")
239
+ return resolved
240
+
241
+
242
+ def run_package_install_rehearsal(
243
+ *,
244
+ package_spec: str,
245
+ repo_path: Path | None = None,
246
+ local_command: Sequence[str] | None = None,
247
+ python: Path | None = None,
248
+ work_dir: Path | None = None,
249
+ timeout: int = 180,
250
+ shadow_cycles: int = 1,
251
+ standalone_default_cycles: int = 1,
252
+ pip_index_url: str = "",
253
+ pip_extra_index_urls: Sequence[str] | None = None,
254
+ ) -> dict[str, Any]:
255
+ requested_package_spec = package_spec
256
+ package_spec = _resolve_install_package_spec(package_spec)
257
+ if work_dir is None:
258
+ work_dir = Path(tempfile.mkdtemp(prefix="code-mower-package-install-"))
259
+ else:
260
+ work_dir = work_dir.expanduser().resolve()
261
+ venv_dir = work_dir / "venv"
262
+ toy_repo = work_dir / "toy-repo"
263
+ outputs = work_dir / "outputs"
264
+ if venv_dir.exists() or toy_repo.exists():
265
+ raise ValueError(f"work directory is not clean: {work_dir}")
266
+ work_dir.mkdir(parents=True, exist_ok=True)
267
+ outputs.mkdir(parents=True, exist_ok=True)
268
+
269
+ python_bin = _resolve_python_executable(python)
270
+ steps: list[dict[str, Any]] = []
271
+
272
+ _run_rehearsal_step(
273
+ [str(python_bin), "-m", "venv", str(venv_dir)],
274
+ cwd=work_dir,
275
+ env=None,
276
+ steps=steps,
277
+ timeout=timeout,
278
+ )
279
+ venv_python = _venv_python(venv_dir)
280
+ code_mower_bin = _venv_code_mower(venv_dir)
281
+ _run_rehearsal_step(
282
+ [str(venv_python), "-m", "pip", "install", "--upgrade", "pip"],
283
+ cwd=work_dir,
284
+ env=None,
285
+ steps=steps,
286
+ timeout=timeout,
287
+ )
288
+ _run_rehearsal_step(
289
+ _pip_install_command(
290
+ venv_python,
291
+ package_spec,
292
+ pip_index_url=pip_index_url,
293
+ pip_extra_index_urls=pip_extra_index_urls,
294
+ ),
295
+ cwd=work_dir,
296
+ env=None,
297
+ steps=steps,
298
+ timeout=timeout,
299
+ )
300
+ _run_rehearsal_step(
301
+ [str(venv_python), "-m", "pip", "check"],
302
+ cwd=work_dir,
303
+ env=None,
304
+ steps=steps,
305
+ timeout=timeout,
306
+ )
307
+ version = _run_rehearsal_step(
308
+ [str(code_mower_bin), "--version"],
309
+ cwd=work_dir,
310
+ env=None,
311
+ steps=steps,
312
+ timeout=timeout,
313
+ ).stdout.strip()
314
+
315
+ env = os.environ.copy()
316
+ env["PATH"] = f"{code_mower_bin.parent}{os.pathsep}{env.get('PATH', '')}"
317
+ _write_public_rehearsal_toy_repo(
318
+ toy_repo,
319
+ steps=steps,
320
+ env=env,
321
+ timeout=timeout,
322
+ )
323
+ _run_rehearsal_step(
324
+ [str(code_mower_bin), "providers", "list"],
325
+ cwd=toy_repo,
326
+ env=env,
327
+ steps=steps,
328
+ timeout=timeout,
329
+ )
330
+ _run_rehearsal_step(
331
+ [
332
+ str(code_mower_bin),
333
+ "init",
334
+ "--easy",
335
+ "--apply",
336
+ "--output-dir",
337
+ ".code-mower.generated",
338
+ "--json",
339
+ ],
340
+ cwd=toy_repo,
341
+ env=env,
342
+ steps=steps,
343
+ timeout=timeout,
344
+ )
345
+ _run_rehearsal_step(
346
+ ["bash", ".code-mower.generated/smoke-tests.sh"],
347
+ cwd=toy_repo,
348
+ env=env,
349
+ steps=steps,
350
+ timeout=timeout,
351
+ )
352
+ _run_rehearsal_step(
353
+ [str(code_mower_bin), "doctor", "--easy", "--json"],
354
+ cwd=toy_repo,
355
+ env=env,
356
+ steps=steps,
357
+ timeout=timeout,
358
+ )
359
+ _run_rehearsal_step(
360
+ [str(code_mower_bin), "next-steps", "--profile", "recommended", "--json"],
361
+ cwd=toy_repo,
362
+ env=env,
363
+ steps=steps,
364
+ timeout=timeout,
365
+ )
366
+ _run_rehearsal_step(
367
+ [
368
+ str(code_mower_bin),
369
+ "migration",
370
+ "wrapper-rehearsal",
371
+ "--repo-path",
372
+ str(toy_repo),
373
+ "--local-command",
374
+ str(code_mower_bin),
375
+ "--package-command",
376
+ str(code_mower_bin),
377
+ "--json",
378
+ ],
379
+ cwd=toy_repo,
380
+ env=env,
381
+ steps=steps,
382
+ timeout=timeout,
383
+ )
384
+ _run_rehearsal_step_to_file(
385
+ [
386
+ str(code_mower_bin),
387
+ "calibration",
388
+ "plan",
389
+ ".code-mower.generated/calibration-corpus.json",
390
+ "--replicates",
391
+ "2",
392
+ "--json",
393
+ ],
394
+ cwd=toy_repo,
395
+ env=env,
396
+ steps=steps,
397
+ timeout=timeout,
398
+ stdout_path=toy_repo / ".code-mower" / "calibration-plan.json",
399
+ )
400
+ auto_discovery_input = toy_repo / ".code-mower" / "auto-discovery-prs.json"
401
+ _write_rehearsal_auto_discovery_fixture(auto_discovery_input)
402
+ _run_rehearsal_step_to_file(
403
+ [
404
+ str(code_mower_bin),
405
+ "calibration",
406
+ "auto-discover",
407
+ "--repo",
408
+ "example/toy-repo",
409
+ "--input",
410
+ str(auto_discovery_input),
411
+ "--output",
412
+ ".code-mower/draft-calibration-corpus.json",
413
+ "--json",
414
+ ],
415
+ cwd=toy_repo,
416
+ env=env,
417
+ steps=steps,
418
+ timeout=timeout,
419
+ stdout_path=outputs / "auto-discover.json",
420
+ )
421
+ _run_rehearsal_step_to_file(
422
+ [
423
+ str(code_mower_bin),
424
+ "calibration",
425
+ "value-report",
426
+ ".code-mower/draft-calibration-corpus.json",
427
+ "--output",
428
+ ".code-mower/draft-reviewer-value-report.md",
429
+ ],
430
+ cwd=toy_repo,
431
+ env=env,
432
+ steps=steps,
433
+ timeout=timeout,
434
+ stdout_path=outputs / "draft-value-report.txt",
435
+ )
436
+ _run_rehearsal_step_to_file(
437
+ [
438
+ str(code_mower_bin),
439
+ "calibration",
440
+ "evidence",
441
+ ".code-mower.generated/calibration-corpus.json",
442
+ "--json",
443
+ ],
444
+ cwd=toy_repo,
445
+ env=env,
446
+ steps=steps,
447
+ timeout=timeout,
448
+ stdout_path=toy_repo / "calibration-evidence.json",
449
+ )
450
+ _run_rehearsal_step_to_file(
451
+ [
452
+ str(code_mower_bin),
453
+ "reviewer-metrics",
454
+ "calibration-evidence.json",
455
+ "--spend",
456
+ ".code-mower.generated/reviewer-spend.json",
457
+ "--json",
458
+ ],
459
+ cwd=toy_repo,
460
+ env=env,
461
+ steps=steps,
462
+ timeout=timeout,
463
+ stdout_path=toy_repo / "reviewer-metrics.json",
464
+ )
465
+ _run_rehearsal_step_to_file(
466
+ [
467
+ str(code_mower_bin),
468
+ "calibration",
469
+ "policy",
470
+ "reviewer-metrics.json",
471
+ "--json",
472
+ ],
473
+ cwd=toy_repo,
474
+ env=env,
475
+ steps=steps,
476
+ timeout=timeout,
477
+ stdout_path=toy_repo / "lane-policy.json",
478
+ )
479
+ _run_rehearsal_step_to_file(
480
+ [
481
+ str(code_mower_bin),
482
+ "calibration",
483
+ "value-report",
484
+ ".code-mower.generated/calibration-corpus.json",
485
+ "--spend",
486
+ ".code-mower.generated/reviewer-spend.json",
487
+ "--output",
488
+ "reviewer-value-report.md",
489
+ ],
490
+ cwd=toy_repo,
491
+ env=env,
492
+ steps=steps,
493
+ timeout=timeout,
494
+ stdout_path=outputs / "value-report.txt",
495
+ )
496
+ _run_rehearsal_step_to_file(
497
+ [
498
+ str(code_mower_bin),
499
+ "cloud",
500
+ "export",
501
+ "--report",
502
+ "reviewer-metrics=reviewer-metrics.json",
503
+ "--report",
504
+ "lane-policy=lane-policy.json",
505
+ "--report",
506
+ "value-report=reviewer-value-report.md",
507
+ "--output-dir",
508
+ ".code-mower/cloud-benchmark-bundle",
509
+ "--json",
510
+ ],
511
+ cwd=toy_repo,
512
+ env=env,
513
+ steps=steps,
514
+ timeout=timeout,
515
+ stdout_path=toy_repo / "cloud-export.json",
516
+ )
517
+ _run_rehearsal_step_to_file(
518
+ [
519
+ str(code_mower_bin),
520
+ "cloud",
521
+ "upload",
522
+ ".code-mower/cloud-benchmark-bundle",
523
+ "--dry-run",
524
+ "--json",
525
+ ],
526
+ cwd=toy_repo,
527
+ env=env,
528
+ steps=steps,
529
+ timeout=timeout,
530
+ stdout_path=toy_repo / "cloud-upload-dry-run.json",
531
+ )
532
+ _run_rehearsal_step_to_file(
533
+ [
534
+ str(code_mower_bin),
535
+ "cloud",
536
+ "dogfood",
537
+ "--repo-path",
538
+ str(toy_repo),
539
+ "--repo-slug",
540
+ "example/toy-repo",
541
+ "--source",
542
+ "package-install-rehearsal",
543
+ "--output-dir",
544
+ ".code-mower/cloud-dogfood-bundle",
545
+ "--endpoint",
546
+ "http://localhost:3000/api/ingest",
547
+ "--json",
548
+ ],
549
+ cwd=toy_repo,
550
+ env=env,
551
+ steps=steps,
552
+ timeout=timeout,
553
+ stdout_path=toy_repo / "cloud-dogfood-dry-run.json",
554
+ )
555
+
556
+ product_wrapper_payload: dict[str, Any] | None = None
557
+ product_mirror_payload: dict[str, Any] | None = None
558
+ external_repo_payload: dict[str, Any] | None = None
559
+ if repo_path is not None:
560
+ repo_path = repo_path.expanduser().resolve()
561
+ if not repo_path.is_dir():
562
+ raise ValueError(f"repo path is not a directory: {repo_path}")
563
+ if local_command or _repo_has_product_wrapper(repo_path):
564
+ product_local_command = (
565
+ tuple(local_command)
566
+ if local_command
567
+ else _default_product_rehearsal_local_command(repo_path)
568
+ )
569
+ product_local_command_text = " ".join(product_local_command)
570
+ wrapper_completed = _run_rehearsal_step(
571
+ [
572
+ str(code_mower_bin),
573
+ "migration",
574
+ "wrapper-rehearsal",
575
+ "--repo-path",
576
+ str(repo_path),
577
+ "--local-command",
578
+ product_local_command_text,
579
+ "--package-command",
580
+ str(code_mower_bin),
581
+ "--json",
582
+ ],
583
+ cwd=repo_path,
584
+ env=env,
585
+ steps=steps,
586
+ timeout=timeout,
587
+ )
588
+ product_wrapper_payload = _json_payload(wrapper_completed.stdout)
589
+ if (
590
+ not isinstance(product_wrapper_payload, dict)
591
+ or product_wrapper_payload.get("status") != "pass"
592
+ ):
593
+ raise RehearsalError(
594
+ "product wrapper rehearsal did not pass",
595
+ steps,
596
+ )
597
+ mirror_completed = _run_rehearsal_step(
598
+ [
599
+ str(code_mower_bin),
600
+ "migration",
601
+ "mirror-removal-plan",
602
+ "--repo-path",
603
+ str(repo_path),
604
+ "--shadow-cycles",
605
+ str(shadow_cycles),
606
+ "--standalone-default-cycles",
607
+ str(standalone_default_cycles),
608
+ "--json",
609
+ ],
610
+ cwd=repo_path,
611
+ env=env,
612
+ steps=steps,
613
+ timeout=timeout,
614
+ )
615
+ product_mirror_payload = _json_payload(mirror_completed.stdout)
616
+ else:
617
+ external_repo_payload = _run_external_repo_readiness(
618
+ code_mower_bin=code_mower_bin,
619
+ repo_path=repo_path,
620
+ env=env,
621
+ steps=steps,
622
+ timeout=timeout,
623
+ )
624
+
625
+ readiness = _first_user_readiness_scorecard(
626
+ toy_repo=toy_repo,
627
+ outputs=outputs,
628
+ version=version,
629
+ steps=steps,
630
+ )
631
+ _write_json(outputs / "first-user-readiness.json", readiness)
632
+ if readiness.get("status") != "pass":
633
+ raise RehearsalError("first-user readiness scorecard did not pass", steps)
634
+
635
+ payload = {
636
+ "mode": "code-mower-package-install-rehearsal",
637
+ "status": "pass",
638
+ "package_spec": package_spec,
639
+ "requested_package_spec": requested_package_spec,
640
+ "pip_index_url": pip_index_url,
641
+ "pip_extra_index_urls": list(pip_extra_index_urls or ()),
642
+ "python": str(python_bin),
643
+ "work_dir": str(work_dir),
644
+ "venv_dir": str(venv_dir),
645
+ "code_mower_bin": str(code_mower_bin),
646
+ "version": version,
647
+ "toy_repo": str(toy_repo),
648
+ "first_user_artifacts": _first_user_artifacts(toy_repo),
649
+ "first_user_readiness": readiness,
650
+ "first_user_readiness_path": str(outputs / "first-user-readiness.json"),
651
+ "repo_path": str(repo_path) if repo_path is not None else "",
652
+ "step_count": len(steps),
653
+ "steps": steps,
654
+ "product_wrapper_rehearsal": product_wrapper_payload,
655
+ "product_mirror_removal_plan": product_mirror_payload,
656
+ "external_repo_readiness": external_repo_payload,
657
+ }
658
+ _write_json(outputs / "package-install-rehearsal.json", payload)
659
+ return payload
660
+
661
+
662
+ def render_package_install_rehearsal_text(payload: dict[str, Any]) -> str:
663
+ lines = [
664
+ "Code Mower package-install rehearsal",
665
+ f"Status: {payload['status']}",
666
+ f"Package: {payload['package_spec']}",
667
+ f"Version: {payload.get('version', '')}",
668
+ f"Work dir: {payload['work_dir']}",
669
+ f"Toy repo: {payload['toy_repo']}",
670
+ f"Steps: {payload['step_count']}",
671
+ ]
672
+ readiness = payload.get("first_user_readiness") or {}
673
+ if readiness:
674
+ lines.extend(
675
+ [
676
+ (
677
+ "First-user readiness: "
678
+ f"{readiness.get('status', 'unknown')} "
679
+ f"({readiness.get('passed', 0)}/{readiness.get('total', 0)} passed)"
680
+ ),
681
+ f"Readiness scorecard: {payload.get('first_user_readiness_path', '')}",
682
+ ]
683
+ )
684
+ artifacts = payload.get("first_user_artifacts") or {}
685
+ if artifacts:
686
+ lines.extend(
687
+ [
688
+ f"Value report: {artifacts.get('reviewer_value_report', '')}",
689
+ f"Draft corpus: {artifacts.get('draft_calibration_corpus', '')}",
690
+ f"Draft value report: {artifacts.get('draft_reviewer_value_report', '')}",
691
+ f"Cloud upload dry run: {artifacts.get('cloud_upload_dry_run', '')}",
692
+ f"Cloud dogfood dry run: {artifacts.get('cloud_dogfood_dry_run', '')}",
693
+ ]
694
+ )
695
+ if payload.get("repo_path"):
696
+ lines.extend(
697
+ [
698
+ f"Product repo: {payload['repo_path']}",
699
+ "Product wrapper rehearsal: "
700
+ f"{(payload.get('product_wrapper_rehearsal') or {}).get('status', 'not-run')}",
701
+ "Product mirror-removal status: "
702
+ f"{(payload.get('product_mirror_removal_plan') or {}).get('status', 'not-run')}",
703
+ ]
704
+ )
705
+ external = payload.get("external_repo_readiness") or {}
706
+ if external:
707
+ lines.extend(
708
+ [
709
+ "External repo readiness: "
710
+ f"{external.get('status', 'not-run')} "
711
+ f"({external.get('check_count', 0)} native checks detected)",
712
+ ]
713
+ )
714
+ lines.append("")
715
+ lines.append(
716
+ f"Full JSON: {Path(payload['work_dir']) / 'outputs' / 'package-install-rehearsal.json'}"
717
+ )
718
+ return "\n".join(lines) + "\n"