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,205 @@
1
+ """Local token/config handling for CodeMower.com setup flows."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import shlex
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from .endpoints import validate_upload_endpoint
11
+ from .errors import CloudBundleError
12
+
13
+
14
+ DEFAULT_UPLOAD_ENDPOINT = "https://codemower.com/api/ingest"
15
+ DEFAULT_TOKEN_ENV = "CODE_MOWER_CLOUD_TOKEN"
16
+ DEFAULT_TEAM_ID_ENV = "CODE_MOWER_CLOUD_TEAM_ID"
17
+ DEFAULT_INSTALL_ID_ENV = "CODE_MOWER_INSTALL_ID"
18
+ DEFAULT_SETUP_INSTALL_ID = "code-mower-local"
19
+
20
+
21
+ def token_prefix(token: str) -> str:
22
+ token = token.strip()
23
+ if not token:
24
+ return ""
25
+ visible = min(12, max(4, len(token) // 2), max(0, len(token) - 4))
26
+ if visible <= 0:
27
+ return "<redacted>"
28
+ return token[:visible] + "..."
29
+
30
+
31
+ def safe_config_stem(value: str) -> str:
32
+ stem = "".join(
33
+ char if char.isalnum() or char in {"-", "_", "."} else "-"
34
+ for char in value.strip()
35
+ )
36
+ return stem.strip("-_.") or DEFAULT_SETUP_INSTALL_ID
37
+
38
+
39
+ def default_setup_path(install_id: str) -> Path:
40
+ return (
41
+ Path.home()
42
+ / ".config"
43
+ / "code-mower"
44
+ / "tokens"
45
+ / f"{safe_config_stem(install_id)}.env"
46
+ )
47
+
48
+
49
+ def read_token_file(path: Path) -> str:
50
+ source = path.expanduser()
51
+ if not source.is_file():
52
+ raise CloudBundleError(f"token file does not exist or is not a file: {source}")
53
+ try:
54
+ text = source.read_text(encoding="utf-8")
55
+ except UnicodeDecodeError as exc:
56
+ raise CloudBundleError(f"token file is not UTF-8 text: {source}") from exc
57
+ except OSError as exc:
58
+ raise CloudBundleError(f"unable to read token file {source}: {exc}") from exc
59
+ for line in text.splitlines():
60
+ stripped = line.strip()
61
+ if not stripped or stripped.startswith("#"):
62
+ continue
63
+ if stripped.startswith("export "):
64
+ stripped = stripped.removeprefix("export ").strip()
65
+ if stripped.startswith(f"{DEFAULT_TOKEN_ENV}="):
66
+ return stripped.split("=", 1)[1].strip().strip("'\"")
67
+ return text.strip()
68
+
69
+
70
+ def resolve_setup_token(
71
+ *,
72
+ token: str,
73
+ token_file: Path | None,
74
+ token_stdin: bool,
75
+ token_env: str,
76
+ ) -> str:
77
+ explicit_sources = sum(
78
+ 1 for value in (bool(token), token_file is not None, token_stdin) if value
79
+ )
80
+ if explicit_sources > 1:
81
+ raise CloudBundleError(
82
+ "choose only one token source: --token, --token-file, or --token-stdin"
83
+ )
84
+ if token:
85
+ resolved = token.strip()
86
+ elif token_file is not None:
87
+ resolved = read_token_file(token_file)
88
+ elif token_stdin:
89
+ resolved = sys.stdin.read().strip()
90
+ else:
91
+ resolved = os.environ.get(token_env, "").strip()
92
+ if not resolved:
93
+ raise CloudBundleError(
94
+ "cloud setup needs a token; pass --token-stdin, --token-file, "
95
+ f"or set {token_env}"
96
+ )
97
+ return resolved
98
+
99
+
100
+ def render_setup_env(
101
+ *,
102
+ token: str,
103
+ endpoint: str,
104
+ team_id: str,
105
+ install_id: str,
106
+ ) -> str:
107
+ try:
108
+ validate_upload_endpoint(endpoint)
109
+ except ValueError as exc:
110
+ raise CloudBundleError(str(exc)) from exc
111
+ assignments = {
112
+ DEFAULT_TOKEN_ENV: token.strip(),
113
+ "CODE_MOWER_CLOUD_ENDPOINT": endpoint.strip(),
114
+ DEFAULT_TEAM_ID_ENV: team_id.strip(),
115
+ DEFAULT_INSTALL_ID_ENV: install_id.strip(),
116
+ }
117
+ lines = [
118
+ "# Code Mower Cloud local token file",
119
+ "# Keep this file private. It contains a bearer token.",
120
+ ]
121
+ lines.extend(
122
+ f"export {name}={shlex.quote(value)}"
123
+ for name, value in assignments.items()
124
+ if value
125
+ )
126
+ return "\n".join(lines) + "\n"
127
+
128
+
129
+ def write_setup_env_file(
130
+ *,
131
+ path: Path,
132
+ text: str,
133
+ force: bool = False,
134
+ ) -> None:
135
+ target = path.expanduser()
136
+ if target.exists() and not force:
137
+ raise CloudBundleError(
138
+ f"setup file already exists; pass --force to overwrite: {target}"
139
+ )
140
+ parent_existed = target.parent.exists()
141
+ try:
142
+ target.parent.mkdir(parents=True, exist_ok=True)
143
+ if not parent_existed:
144
+ target.parent.chmod(0o700)
145
+ except OSError as exc:
146
+ raise CloudBundleError(
147
+ f"unable to prepare setup directory {target.parent}: {exc}"
148
+ ) from exc
149
+ flags = os.O_WRONLY | os.O_CREAT
150
+ if not force:
151
+ flags |= os.O_EXCL
152
+ try:
153
+ fd = os.open(target, flags, 0o600)
154
+ with os.fdopen(fd, "w", encoding="utf-8") as handle:
155
+ os.fchmod(handle.fileno(), 0o600)
156
+ handle.truncate(0)
157
+ handle.write(text)
158
+ target.chmod(0o600)
159
+ except FileExistsError as exc:
160
+ raise CloudBundleError(
161
+ f"setup file already exists; pass --force to overwrite: {target}"
162
+ ) from exc
163
+ except OSError as exc:
164
+ raise CloudBundleError(f"unable to write setup file {target}: {exc}") from exc
165
+
166
+
167
+ def run_cloud_setup(
168
+ *,
169
+ token: str,
170
+ token_file: Path | None,
171
+ token_stdin: bool,
172
+ token_env: str,
173
+ endpoint: str,
174
+ team_id: str,
175
+ install_id: str,
176
+ out: Path | None,
177
+ force: bool = False,
178
+ dry_run: bool = False,
179
+ ) -> dict[str, str]:
180
+ resolved_install_id = install_id.strip() or DEFAULT_SETUP_INSTALL_ID
181
+ target = out.expanduser() if out else default_setup_path(resolved_install_id)
182
+ resolved_token = resolve_setup_token(
183
+ token=token,
184
+ token_file=token_file,
185
+ token_stdin=token_stdin,
186
+ token_env=token_env,
187
+ )
188
+ env_text = render_setup_env(
189
+ token=resolved_token,
190
+ endpoint=endpoint,
191
+ team_id=team_id,
192
+ install_id=resolved_install_id,
193
+ )
194
+ if not dry_run:
195
+ write_setup_env_file(path=target, text=env_text, force=force)
196
+ return {
197
+ "mode": "cloud-setup",
198
+ "status": "dry_run" if dry_run else "written",
199
+ "path": str(target),
200
+ "endpoint": endpoint,
201
+ "team_id": team_id,
202
+ "install_id": resolved_install_id,
203
+ "token_prefix": token_prefix(resolved_token),
204
+ "shell": f"source {shlex.quote(str(target))}",
205
+ }
@@ -0,0 +1,97 @@
1
+ """Upload payload construction and network posting for CodeMower.com."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import urllib.error
7
+ import urllib.request
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from .bundle import validate_metadata_payload
12
+ from .endpoints import validate_upload_endpoint
13
+ from .errors import CloudBundleError
14
+ from .manifest import load_bundle_manifest
15
+ from .reports import included_report_payloads
16
+
17
+
18
+ UPLOAD_SCHEMA = "code_mower.cloudUpload.v1"
19
+
20
+
21
+ def _validate_upload_endpoint(endpoint: str) -> None:
22
+ try:
23
+ validate_upload_endpoint(endpoint)
24
+ except ValueError as exc:
25
+ raise CloudBundleError(str(exc)) from exc
26
+
27
+
28
+ def build_upload_payload(
29
+ *,
30
+ bundle_dir: Path,
31
+ include_reports: bool = False,
32
+ ) -> dict[str, Any]:
33
+ bundle_dir = bundle_dir.expanduser()
34
+ if not bundle_dir.is_dir():
35
+ raise CloudBundleError(f"bundle directory does not exist: {bundle_dir}")
36
+ manifest = load_bundle_manifest(bundle_dir)
37
+ validate_metadata_payload(manifest)
38
+ events = manifest.get("events", [])
39
+ return {
40
+ "schema": UPLOAD_SCHEMA,
41
+ "bundle_schema": manifest.get("schema"),
42
+ "privacy_mode": manifest.get("privacy_mode", ""),
43
+ "upload_mode": "reports_included" if include_reports else "metadata_only",
44
+ "repo_slug": manifest.get("repo_slug", ""),
45
+ "team_id": manifest.get("team_id", ""),
46
+ "install_id": manifest.get("install_id", ""),
47
+ "excluded_content": manifest.get("excluded_content", []),
48
+ "reports": included_report_payloads(
49
+ manifest,
50
+ bundle_dir,
51
+ include_reports=include_reports,
52
+ ),
53
+ "events": events,
54
+ "notes": [
55
+ "This upload payload is built from an explicit local bundle.",
56
+ "Report contents are included only when --include-reports is set.",
57
+ ],
58
+ }
59
+
60
+
61
+ def post_upload_payload(
62
+ *,
63
+ payload: dict[str, Any],
64
+ endpoint: str,
65
+ token: str = "",
66
+ timeout: float = 20.0,
67
+ ) -> dict[str, Any]:
68
+ _validate_upload_endpoint(endpoint)
69
+ body = json.dumps(payload, sort_keys=True).encode("utf-8")
70
+ headers = {
71
+ "Content-Type": "application/json",
72
+ "User-Agent": "code-mower-cloud-upload",
73
+ }
74
+ if token:
75
+ headers["Authorization"] = f"Bearer {token}"
76
+ request = urllib.request.Request(endpoint, data=body, headers=headers, method="POST")
77
+ try:
78
+ with urllib.request.urlopen(request, timeout=timeout) as response:
79
+ response_body = response.read().decode("utf-8")
80
+ status = response.getcode()
81
+ except urllib.error.HTTPError as exc:
82
+ detail = exc.read().decode("utf-8", errors="replace")
83
+ raise CloudBundleError(f"upload failed with HTTP {exc.code}: {detail}") from exc
84
+ except urllib.error.URLError as exc:
85
+ raise CloudBundleError(f"upload failed: {exc.reason}") from exc
86
+ try:
87
+ parsed = json.loads(response_body) if response_body else {}
88
+ except json.JSONDecodeError as exc:
89
+ raise CloudBundleError(f"upload response was not JSON: {exc}") from exc
90
+ if not isinstance(parsed, dict):
91
+ raise CloudBundleError("upload response JSON must be an object")
92
+ return {
93
+ "mode": "cloud-upload",
94
+ "endpoint": endpoint,
95
+ "status": status,
96
+ "response": parsed,
97
+ }