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
code_mower/prompts.py ADDED
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env python3
2
+ """Load versioned Code Mower review prompt lenses."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import os
7
+ import argparse
8
+ import json
9
+ import re
10
+ import subprocess
11
+ import sys
12
+ from pathlib import Path
13
+ from typing import Any, Iterable, Mapping
14
+
15
+
16
+ DEFAULT_REVIEW_LENSES = ("base-audit",)
17
+ SAFE_LENS_RE = re.compile(r"^[A-Za-z0-9_.-]+$")
18
+
19
+
20
+ def split_lenses(value: str | Iterable[str] | None) -> tuple[str, ...]:
21
+ """Return normalized prompt lens ids from comma text or an iterable."""
22
+
23
+ if value is None:
24
+ return DEFAULT_REVIEW_LENSES
25
+ if isinstance(value, str):
26
+ lenses = tuple(item.strip() for item in value.split(",") if item.strip())
27
+ else:
28
+ lenses = tuple(str(item).strip() for item in value if str(item).strip())
29
+ return lenses or DEFAULT_REVIEW_LENSES
30
+
31
+
32
+ def default_prompt_dirs() -> tuple[Path, ...]:
33
+ module_dir = Path(__file__).resolve().parent
34
+ return (
35
+ module_dir / "lane_prompts",
36
+ module_dir / "templates" / "lane_prompts",
37
+ module_dir.parent / "tools" / "lane_prompts",
38
+ Path.cwd() / "tools" / "lane_prompts",
39
+ )
40
+
41
+
42
+ def packaged_prompt_dir() -> Path | None:
43
+ module_dir = Path(__file__).resolve().parent
44
+ if module_dir.name == "tools":
45
+ return None
46
+ candidate = module_dir / "templates" / "lane_prompts"
47
+ return candidate if candidate.is_dir() else None
48
+
49
+
50
+ def resolve_prompt_dir(prompt_dir: str | Path | None = None) -> Path:
51
+ if prompt_dir is not None and str(prompt_dir).strip():
52
+ return Path(prompt_dir).expanduser()
53
+
54
+ env_dir = os.environ.get("CODE_MOWER_PROMPT_DIR")
55
+ if env_dir:
56
+ return Path(env_dir).expanduser()
57
+
58
+ for candidate in default_prompt_dirs():
59
+ if candidate.is_dir():
60
+ return candidate
61
+ return default_prompt_dirs()[0]
62
+
63
+
64
+ def _read_lens(prompt_dir: Path, lens: str) -> str:
65
+ if not SAFE_LENS_RE.fullmatch(lens):
66
+ raise ValueError(
67
+ "review lens ids must match [A-Za-z0-9_.-]+: "
68
+ f"{lens!r}"
69
+ )
70
+ path = prompt_dir / f"{lens}.md"
71
+ if not path.is_file():
72
+ raise FileNotFoundError(f"review lens not found: {path}")
73
+ return path.read_text(encoding="utf-8").strip()
74
+
75
+
76
+ def _lens_title(text: str, lens_id: str) -> str:
77
+ for line in text.splitlines():
78
+ stripped = line.strip()
79
+ if stripped.startswith("# "):
80
+ return stripped[2:].strip() or lens_id
81
+ return lens_id
82
+
83
+
84
+ def list_prompt_lenses(prompt_dir: str | Path | None = None) -> list[dict[str, Any]]:
85
+ root = resolve_prompt_dir(prompt_dir)
86
+ if not root.is_dir():
87
+ raise FileNotFoundError(f"prompt directory not found: {root}")
88
+ lenses: list[dict[str, Any]] = []
89
+ for path in sorted(root.glob("*.md")):
90
+ lens_id = path.stem
91
+ if not SAFE_LENS_RE.fullmatch(lens_id):
92
+ continue
93
+ text = path.read_text(encoding="utf-8")
94
+ lenses.append(
95
+ {
96
+ "id": lens_id,
97
+ "title": _lens_title(text, lens_id),
98
+ "path": str(path),
99
+ "bytes": path.stat().st_size,
100
+ }
101
+ )
102
+ return lenses
103
+
104
+
105
+ def _read_git_lens(repo_root: Path, trusted_git_ref: str, lens: str) -> str | None:
106
+ if not SAFE_LENS_RE.fullmatch(lens):
107
+ raise ValueError(
108
+ "review lens ids must match [A-Za-z0-9_.-]+: "
109
+ f"{lens!r}"
110
+ )
111
+ result = subprocess.run(
112
+ [
113
+ "git",
114
+ "-C",
115
+ str(repo_root),
116
+ "show",
117
+ f"{trusted_git_ref}:tools/lane_prompts/{lens}.md",
118
+ ],
119
+ text=True,
120
+ capture_output=True,
121
+ check=False,
122
+ )
123
+ if result.returncode != 0:
124
+ return None
125
+ return result.stdout.strip()
126
+
127
+
128
+ def load_review_prompt(
129
+ lenses: str | Iterable[str] | None = None,
130
+ *,
131
+ prompt_dir: str | Path | None = None,
132
+ trusted_git_ref: str | None = None,
133
+ repo_root: str | Path | None = None,
134
+ missing_ok: bool = False,
135
+ ) -> str:
136
+ """Load and concatenate one or more trusted review prompt lenses."""
137
+
138
+ explicit_prompt_dir = prompt_dir is not None or bool(os.environ.get("CODE_MOWER_PROMPT_DIR"))
139
+ sections = []
140
+ for lens in split_lenses(lenses):
141
+ text: str | None = None
142
+ if not explicit_prompt_dir and trusted_git_ref and repo_root is not None:
143
+ text = _read_git_lens(Path(repo_root), trusted_git_ref, lens)
144
+ if text is None:
145
+ package_dir = packaged_prompt_dir()
146
+ if package_dir is not None:
147
+ try:
148
+ text = _read_lens(package_dir, lens)
149
+ except FileNotFoundError:
150
+ text = None
151
+ if text is None and not missing_ok:
152
+ raise FileNotFoundError(
153
+ "review lens not found in trusted git ref: "
154
+ f"{trusted_git_ref}:tools/lane_prompts/{lens}.md"
155
+ )
156
+ else:
157
+ root = resolve_prompt_dir(prompt_dir)
158
+ try:
159
+ text = _read_lens(root, lens)
160
+ except FileNotFoundError:
161
+ if not missing_ok:
162
+ raise
163
+ text = None
164
+ if text is not None:
165
+ sections.append(f"## Review Lens: {lens}\n\n{text}")
166
+ if not sections and missing_ok:
167
+ return ""
168
+ return "\n\n".join(sections).strip() + "\n"
169
+
170
+
171
+ def append_review_prompt(system_prompt: str, review_prompt: str) -> str:
172
+ """Append trusted review doctrine without changing a prompt's opening line."""
173
+
174
+ if not review_prompt.strip():
175
+ return system_prompt
176
+ return (
177
+ system_prompt.rstrip()
178
+ + "\n\n# Code Mower Review Doctrine\n\n"
179
+ + review_prompt.strip()
180
+ + "\n"
181
+ )
182
+
183
+
184
+ def render_prompt_lenses_text(lenses: Iterable[Mapping[str, Any]]) -> str:
185
+ lines = ["Code Mower prompt lenses", ""]
186
+ count = 0
187
+ for lens in lenses:
188
+ count += 1
189
+ lines.append(f"- {lens.get('id')}: {lens.get('title')}")
190
+ if count == 0:
191
+ lines.append("- none")
192
+ return "\n".join(lines) + "\n"
193
+
194
+
195
+ def main(argv: list[str] | None = None) -> int:
196
+ parser = argparse.ArgumentParser(description=__doc__)
197
+ subparsers = parser.add_subparsers(dest="command", required=True)
198
+
199
+ list_parser = subparsers.add_parser("list")
200
+ list_parser.add_argument("--prompt-dir", default=None)
201
+ list_parser.add_argument("--json", action="store_true")
202
+
203
+ show_parser = subparsers.add_parser("show")
204
+ show_parser.add_argument("lenses", nargs="?", default=",".join(DEFAULT_REVIEW_LENSES))
205
+ show_parser.add_argument("--prompt-dir", default=None)
206
+ show_parser.add_argument("--json", action="store_true")
207
+
208
+ validate_parser = subparsers.add_parser("validate")
209
+ validate_parser.add_argument("--lenses", default=",".join(DEFAULT_REVIEW_LENSES))
210
+ validate_parser.add_argument("--prompt-dir", default=None)
211
+ validate_parser.add_argument("--json", action="store_true")
212
+
213
+ args = parser.parse_args(argv)
214
+
215
+ try:
216
+ if args.command == "list":
217
+ lenses = list_prompt_lenses(args.prompt_dir)
218
+ payload = {
219
+ "mode": "code-mower-prompt-lenses",
220
+ "prompt_dir": str(resolve_prompt_dir(args.prompt_dir)),
221
+ "lenses": lenses,
222
+ }
223
+ if args.json:
224
+ print(json.dumps(payload, indent=2, sort_keys=True))
225
+ else:
226
+ print(render_prompt_lenses_text(lenses), end="")
227
+ return 0
228
+
229
+ if args.command == "show":
230
+ selected = split_lenses(args.lenses)
231
+ prompt = load_review_prompt(selected, prompt_dir=args.prompt_dir)
232
+ payload = {
233
+ "mode": "code-mower-review-prompt",
234
+ "prompt_dir": str(resolve_prompt_dir(args.prompt_dir)),
235
+ "lenses": list(selected),
236
+ "prompt": prompt,
237
+ }
238
+ if args.json:
239
+ print(json.dumps(payload, indent=2, sort_keys=True))
240
+ else:
241
+ print(prompt, end="")
242
+ return 0
243
+
244
+ if args.command == "validate":
245
+ selected = split_lenses(args.lenses)
246
+ prompt = load_review_prompt(selected, prompt_dir=args.prompt_dir)
247
+ payload = {
248
+ "mode": "code-mower-prompt-validation",
249
+ "ok": True,
250
+ "prompt_dir": str(resolve_prompt_dir(args.prompt_dir)),
251
+ "lenses": list(selected),
252
+ "bytes": len(prompt.encode("utf-8")),
253
+ }
254
+ if args.json:
255
+ print(json.dumps(payload, indent=2, sort_keys=True))
256
+ else:
257
+ print("ok")
258
+ return 0
259
+ except (FileNotFoundError, ValueError) as exc:
260
+ print(f"error: {exc}", file=sys.stderr)
261
+ return 1
262
+
263
+ raise AssertionError(f"unhandled prompt command: {args.command}")
264
+
265
+
266
+ if __name__ == "__main__":
267
+ raise SystemExit(main())