code-mower 0.5.0b5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- code_mower/__init__.py +3 -0
- code_mower/adapters/__init__.py +39 -0
- code_mower/adapters/_base.py +148 -0
- code_mower/adapters/cursor_bugbot.py +97 -0
- code_mower/adapters/gitar.py +111 -0
- code_mower/adapters/greptile.py +191 -0
- code_mower/adapters/qodo.py +140 -0
- code_mower/antigravity_cli_audit_pr.py +241 -0
- code_mower/audit_handoff_log.py +345 -0
- code_mower/audit_labeler_lib.py +311 -0
- code_mower/audit_progress.py +203 -0
- code_mower/blind_review_artifacts.py +562 -0
- code_mower/blind_review_coordinator.py +276 -0
- code_mower/bootstrap.py +524 -0
- code_mower/builder_experiment.py +539 -0
- code_mower/calibration/__init__.py +181 -0
- code_mower/calibration/arms.py +267 -0
- code_mower/calibration/auto_discovery.py +348 -0
- code_mower/calibration/commands.py +192 -0
- code_mower/calibration/context_inputs.py +199 -0
- code_mower/calibration/corpus.py +91 -0
- code_mower/calibration/evidence.py +20 -0
- code_mower/calibration/evidence_report.py +360 -0
- code_mower/calibration/identity.py +27 -0
- code_mower/calibration/metrics.py +18 -0
- code_mower/calibration/overlap.py +103 -0
- code_mower/calibration/planning.py +309 -0
- code_mower/calibration/policy.py +207 -0
- code_mower/calibration/results.py +315 -0
- code_mower/calibration/run_results.py +147 -0
- code_mower/calibration/run_status.py +64 -0
- code_mower/calibration/runner.py +360 -0
- code_mower/calibration/truth.py +188 -0
- code_mower/calibration/value_report.py +142 -0
- code_mower/checks.py +402 -0
- code_mower/claude_audit_pr.py +1126 -0
- code_mower/claude_cli_bounce.py +303 -0
- code_mower/claude_cli_environment.py +73 -0
- code_mower/clear_stale.py +374 -0
- code_mower/cli.py +537 -0
- code_mower/cloud.py +674 -0
- code_mower/cloud_client/__init__.py +173 -0
- code_mower/cloud_client/bundle.py +155 -0
- code_mower/cloud_client/doctor.py +206 -0
- code_mower/cloud_client/dogfood.py +78 -0
- code_mower/cloud_client/endpoints.py +114 -0
- code_mower/cloud_client/errors.py +7 -0
- code_mower/cloud_client/events.py +278 -0
- code_mower/cloud_client/export.py +272 -0
- code_mower/cloud_client/git_metadata.py +46 -0
- code_mower/cloud_client/manifest.py +39 -0
- code_mower/cloud_client/operations.py +448 -0
- code_mower/cloud_client/reports.py +45 -0
- code_mower/cloud_client/setup.py +205 -0
- code_mower/cloud_client/upload.py +97 -0
- code_mower/code_mower_calibration.py +598 -0
- code_mower/code_mower_context_packs.py +591 -0
- code_mower/code_mower_merge.py +227 -0
- code_mower/code_mower_telemetry.py +561 -0
- code_mower/coderabbit_cli_audit_pr.py +526 -0
- code_mower/codex_audit_env_preflight.py +220 -0
- code_mower/codex_audit_pr.py +1738 -0
- code_mower/codex_audit_schema_smoke.py +160 -0
- code_mower/codex_audit_verdict.schema.json +44 -0
- code_mower/config.py +655 -0
- code_mower/doctor.py +161 -0
- code_mower/doctor_checks/__init__.py +104 -0
- code_mower/doctor_checks/cloud.py +129 -0
- code_mower/doctor_checks/common.py +215 -0
- code_mower/doctor_checks/github.py +128 -0
- code_mower/doctor_checks/github_actions.py +11 -0
- code_mower/doctor_checks/github_actions_cost.py +99 -0
- code_mower/doctor_checks/github_actions_cost_summary.py +111 -0
- code_mower/doctor_checks/github_actions_failure_annotations.py +27 -0
- code_mower/doctor_checks/github_actions_failure_models.py +47 -0
- code_mower/doctor_checks/github_actions_failure_scan.py +200 -0
- code_mower/doctor_checks/github_actions_failure_selection.py +63 -0
- code_mower/doctor_checks/github_actions_failures.py +103 -0
- code_mower/doctor_checks/github_actions_permissions.py +55 -0
- code_mower/doctor_checks/github_api.py +79 -0
- code_mower/doctor_checks/github_branch.py +56 -0
- code_mower/doctor_checks/github_config.py +25 -0
- code_mower/doctor_checks/github_provider.py +61 -0
- code_mower/doctor_checks/github_repo.py +120 -0
- code_mower/doctor_checks/groups.py +36 -0
- code_mower/doctor_checks/models.py +96 -0
- code_mower/doctor_checks/output.py +86 -0
- code_mower/doctor_checks/presets.py +64 -0
- code_mower/doctor_checks/privacy.py +20 -0
- code_mower/doctor_checks/provider_api_model.py +138 -0
- code_mower/doctor_checks/provider_api_model_openai.py +29 -0
- code_mower/doctor_checks/provider_api_model_profiles.py +137 -0
- code_mower/doctor_checks/provider_env.py +113 -0
- code_mower/doctor_checks/provider_env_required.py +56 -0
- code_mower/doctor_checks/provider_env_tokens.py +100 -0
- code_mower/doctor_checks/provider_local_cli.py +162 -0
- code_mower/doctor_checks/provider_local_cli_commands.py +47 -0
- code_mower/doctor_checks/provider_local_cli_probe_config.py +70 -0
- code_mower/doctor_checks/provider_probe.py +20 -0
- code_mower/doctor_checks/provider_probe_auth.py +52 -0
- code_mower/doctor_checks/provider_probe_evaluation.py +109 -0
- code_mower/doctor_checks/provider_probe_json.py +45 -0
- code_mower/doctor_checks/provider_probe_remediation.py +39 -0
- code_mower/doctor_checks/providers.py +159 -0
- code_mower/doctor_checks/registry.py +69 -0
- code_mower/doctor_checks/runner.py +188 -0
- code_mower/doctor_checks/runtime.py +89 -0
- code_mower/doctor_checks/runtime_github_auth.py +148 -0
- code_mower/gemini_cli_audit_pr.py +897 -0
- code_mower/hermes_cli_audit_pr.py +436 -0
- code_mower/init.py +888 -0
- code_mower/lane_configs/__init__.py +37 -0
- code_mower/lane_configs/aider.py +32 -0
- code_mower/lane_configs/antigravity_cli.py +35 -0
- code_mower/lane_configs/claude.py +35 -0
- code_mower/lane_configs/codex.py +32 -0
- code_mower/lane_configs/devin.py +33 -0
- code_mower/lane_configs/gemini_cli.py +35 -0
- code_mower/lane_configs/hermes_cli.py +35 -0
- code_mower/lane_configs/local_llm.py +31 -0
- code_mower/local_llm_audit_pr.py +1364 -0
- code_mower/local_llm_bakeoff.py +458 -0
- code_mower/local_llm_calibration.py +441 -0
- code_mower/local_llm_profiles.py +66 -0
- code_mower/migration.py +508 -0
- code_mower/migration_install.py +292 -0
- code_mower/migration_mirror.py +392 -0
- code_mower/migration_readiness.py +237 -0
- code_mower/migration_rehearsal.py +718 -0
- code_mower/next_steps.py +441 -0
- code_mower/package.py +673 -0
- code_mower/package_content.py +444 -0
- code_mower/package_manifest.py +452 -0
- code_mower/package_paths.py +53 -0
- code_mower/package_rendering.py +90 -0
- code_mower/package_static.py +585 -0
- code_mower/prompts.py +267 -0
- code_mower/provider_registry.py +469 -0
- code_mower/provider_runners/__init__.py +60 -0
- code_mower/provider_runners/comments.py +31 -0
- code_mower/provider_runners/git.py +46 -0
- code_mower/provider_runners/github_auth.py +61 -0
- code_mower/provider_runners/github_pr.py +120 -0
- code_mower/provider_runners/process.py +58 -0
- code_mower/provider_runners/repo_paths.py +23 -0
- code_mower/provider_runners/text_schema.py +41 -0
- code_mower/provider_runners/verdict_artifacts.py +103 -0
- code_mower/provider_runners/workspace.py +57 -0
- code_mower/release_readiness.py +549 -0
- code_mower/reviewer_metrics.py +389 -0
- code_mower/saas_reviewer_labeler.py +809 -0
- code_mower/secrets.py +89 -0
- code_mower/templates/builder-experiment.example.json +55 -0
- code_mower/templates/calibration-corpus.example.json +129 -0
- code_mower/templates/calibration-corpus.json +129 -0
- code_mower/templates/code-mower.example.yml +423 -0
- code_mower/templates/context-packs.example.json +150 -0
- code_mower/templates/lane_prompts/base-audit.md +22 -0
- code_mower/templates/lane_prompts/calibration-policy.md +21 -0
- code_mower/templates/lane_prompts/context-driven-quality.md +21 -0
- code_mower/templates/lane_prompts/docs-design.md +12 -0
- code_mower/templates/lane_prompts/generic-programming.md +21 -0
- code_mower/templates/lane_prompts/operability.md +22 -0
- code_mower/templates/lane_prompts/package-runtime.md +12 -0
- code_mower/templates/lane_prompts/security-threat-model.md +22 -0
- code_mower/templates/product-support/code_mower +216 -0
- code_mower/templates/product-support/code_mower_standalone_pin.env +7 -0
- code_mower/templates/product-support/code_mower_standalone_shadow.sh +151 -0
- code_mower/templates/product-support/run_claude_audit_pr.sh +32 -0
- code_mower/templates/product-support/run_codex_audit_pr.sh +32 -0
- code_mower/templates/product-support/safe_gh_comment.py +96 -0
- code_mower/templates/providers.yml +454 -0
- code_mower/templates/reviewer-spend.example.json +28 -0
- code_mower/templates/reviewer-value-report.example.md +20 -0
- code_mower/templates/workflows/private-standalone-shadow.yml.j2 +106 -0
- code_mower/templates/workflows/review-clear-stale.yml.j2 +83 -0
- code_mower/trailer_comment_labeler.py +207 -0
- code_mower/versioning.py +32 -0
- code_mower-0.5.0b5.dist-info/METADATA +302 -0
- code_mower-0.5.0b5.dist-info/RECORD +185 -0
- code_mower-0.5.0b5.dist-info/WHEEL +5 -0
- code_mower-0.5.0b5.dist-info/entry_points.txt +2 -0
- code_mower-0.5.0b5.dist-info/licenses/LICENSE +202 -0
- code_mower-0.5.0b5.dist-info/licenses/NOTICE +10 -0
- code_mower-0.5.0b5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""CodeMower.com client internals."""
|
|
2
|
+
|
|
3
|
+
from .bundle import (
|
|
4
|
+
BUNDLE_MANIFEST_FILENAME,
|
|
5
|
+
BUNDLE_SCHEMA,
|
|
6
|
+
EXCLUDED_CONTENT,
|
|
7
|
+
EXPECTED_BUNDLE_ENTRIES,
|
|
8
|
+
MAX_EVENT_COUNT,
|
|
9
|
+
MAX_REPORT_UPLOAD_BYTES,
|
|
10
|
+
SAFE_EVENT_TYPES,
|
|
11
|
+
SAFE_REPORT_KINDS,
|
|
12
|
+
is_bundle_manifest,
|
|
13
|
+
validate_metadata_payload,
|
|
14
|
+
)
|
|
15
|
+
from .endpoints import (
|
|
16
|
+
DEFAULT_DASHBOARD_PATH,
|
|
17
|
+
DEFAULT_HEALTH_PATH,
|
|
18
|
+
dashboard_url_for_endpoint,
|
|
19
|
+
health_url_for_endpoint,
|
|
20
|
+
is_local_http_endpoint,
|
|
21
|
+
probe_cloud_service,
|
|
22
|
+
validate_upload_endpoint,
|
|
23
|
+
)
|
|
24
|
+
from .export import (
|
|
25
|
+
build_cloud_bundle,
|
|
26
|
+
existing_bundle_manifest,
|
|
27
|
+
plan_report,
|
|
28
|
+
render_bundle_readme,
|
|
29
|
+
safe_target_name,
|
|
30
|
+
stage_reports,
|
|
31
|
+
swap_reports,
|
|
32
|
+
unexpected_bundle_entries,
|
|
33
|
+
)
|
|
34
|
+
from .dogfood import (
|
|
35
|
+
DEFAULT_DOGFOOD_REPORTS,
|
|
36
|
+
DogfoodPlan,
|
|
37
|
+
build_dogfood_dry_run_preview,
|
|
38
|
+
build_dogfood_plan,
|
|
39
|
+
default_dogfood_reports,
|
|
40
|
+
)
|
|
41
|
+
from .doctor import (
|
|
42
|
+
render_cloud_doctor_text,
|
|
43
|
+
run_cloud_doctor,
|
|
44
|
+
)
|
|
45
|
+
from .events import (
|
|
46
|
+
EVENT_SCHEMA,
|
|
47
|
+
GITHUB_RUN_LIST_FIELDS,
|
|
48
|
+
build_dogfood_event,
|
|
49
|
+
build_workflow_run_event,
|
|
50
|
+
event_id_from_github_run,
|
|
51
|
+
load_event_file,
|
|
52
|
+
normalize_event,
|
|
53
|
+
parse_event_args,
|
|
54
|
+
run_gh_run_list,
|
|
55
|
+
safe_event_type,
|
|
56
|
+
safe_kind,
|
|
57
|
+
utc_now,
|
|
58
|
+
)
|
|
59
|
+
from .git_metadata import (
|
|
60
|
+
detect_repo_slug,
|
|
61
|
+
repo_slug_from_remote,
|
|
62
|
+
run_git,
|
|
63
|
+
)
|
|
64
|
+
from .manifest import (
|
|
65
|
+
load_bundle_manifest,
|
|
66
|
+
report_path_from_manifest,
|
|
67
|
+
)
|
|
68
|
+
from .reports import included_report_payloads
|
|
69
|
+
from .operations import (
|
|
70
|
+
catch_up_upload,
|
|
71
|
+
dogfood_upload,
|
|
72
|
+
parse_repo_sync_spec,
|
|
73
|
+
repo_sync_output_name,
|
|
74
|
+
repo_sync_upload,
|
|
75
|
+
reviewer_runs_upload,
|
|
76
|
+
)
|
|
77
|
+
from .errors import CloudBundleError
|
|
78
|
+
from .setup import (
|
|
79
|
+
DEFAULT_INSTALL_ID_ENV,
|
|
80
|
+
DEFAULT_SETUP_INSTALL_ID,
|
|
81
|
+
DEFAULT_TEAM_ID_ENV,
|
|
82
|
+
DEFAULT_TOKEN_ENV,
|
|
83
|
+
DEFAULT_UPLOAD_ENDPOINT,
|
|
84
|
+
default_setup_path,
|
|
85
|
+
read_token_file,
|
|
86
|
+
render_setup_env,
|
|
87
|
+
resolve_setup_token,
|
|
88
|
+
run_cloud_setup,
|
|
89
|
+
safe_config_stem,
|
|
90
|
+
token_prefix,
|
|
91
|
+
write_setup_env_file,
|
|
92
|
+
)
|
|
93
|
+
from .upload import (
|
|
94
|
+
UPLOAD_SCHEMA,
|
|
95
|
+
build_upload_payload,
|
|
96
|
+
post_upload_payload,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
__all__ = [
|
|
100
|
+
"BUNDLE_MANIFEST_FILENAME",
|
|
101
|
+
"BUNDLE_SCHEMA",
|
|
102
|
+
"DEFAULT_DASHBOARD_PATH",
|
|
103
|
+
"DEFAULT_HEALTH_PATH",
|
|
104
|
+
"EXCLUDED_CONTENT",
|
|
105
|
+
"EXPECTED_BUNDLE_ENTRIES",
|
|
106
|
+
"DEFAULT_DOGFOOD_REPORTS",
|
|
107
|
+
"MAX_EVENT_COUNT",
|
|
108
|
+
"MAX_REPORT_UPLOAD_BYTES",
|
|
109
|
+
"SAFE_EVENT_TYPES",
|
|
110
|
+
"SAFE_REPORT_KINDS",
|
|
111
|
+
"UPLOAD_SCHEMA",
|
|
112
|
+
"CloudBundleError",
|
|
113
|
+
"DEFAULT_INSTALL_ID_ENV",
|
|
114
|
+
"DEFAULT_SETUP_INSTALL_ID",
|
|
115
|
+
"DEFAULT_TEAM_ID_ENV",
|
|
116
|
+
"DEFAULT_TOKEN_ENV",
|
|
117
|
+
"DEFAULT_UPLOAD_ENDPOINT",
|
|
118
|
+
"DogfoodPlan",
|
|
119
|
+
"EVENT_SCHEMA",
|
|
120
|
+
"GITHUB_RUN_LIST_FIELDS",
|
|
121
|
+
"build_dogfood_event",
|
|
122
|
+
"build_dogfood_dry_run_preview",
|
|
123
|
+
"build_dogfood_plan",
|
|
124
|
+
"build_workflow_run_event",
|
|
125
|
+
"build_cloud_bundle",
|
|
126
|
+
"build_upload_payload",
|
|
127
|
+
"catch_up_upload",
|
|
128
|
+
"dashboard_url_for_endpoint",
|
|
129
|
+
"default_setup_path",
|
|
130
|
+
"detect_repo_slug",
|
|
131
|
+
"default_dogfood_reports",
|
|
132
|
+
"dogfood_upload",
|
|
133
|
+
"event_id_from_github_run",
|
|
134
|
+
"existing_bundle_manifest",
|
|
135
|
+
"health_url_for_endpoint",
|
|
136
|
+
"included_report_payloads",
|
|
137
|
+
"is_local_http_endpoint",
|
|
138
|
+
"is_bundle_manifest",
|
|
139
|
+
"load_bundle_manifest",
|
|
140
|
+
"load_event_file",
|
|
141
|
+
"normalize_event",
|
|
142
|
+
"plan_report",
|
|
143
|
+
"parse_repo_sync_spec",
|
|
144
|
+
"parse_event_args",
|
|
145
|
+
"post_upload_payload",
|
|
146
|
+
"probe_cloud_service",
|
|
147
|
+
"read_token_file",
|
|
148
|
+
"render_bundle_readme",
|
|
149
|
+
"render_cloud_doctor_text",
|
|
150
|
+
"render_setup_env",
|
|
151
|
+
"repo_slug_from_remote",
|
|
152
|
+
"repo_sync_output_name",
|
|
153
|
+
"repo_sync_upload",
|
|
154
|
+
"report_path_from_manifest",
|
|
155
|
+
"resolve_setup_token",
|
|
156
|
+
"reviewer_runs_upload",
|
|
157
|
+
"run_cloud_doctor",
|
|
158
|
+
"run_cloud_setup",
|
|
159
|
+
"run_gh_run_list",
|
|
160
|
+
"run_git",
|
|
161
|
+
"safe_target_name",
|
|
162
|
+
"safe_config_stem",
|
|
163
|
+
"safe_event_type",
|
|
164
|
+
"safe_kind",
|
|
165
|
+
"stage_reports",
|
|
166
|
+
"swap_reports",
|
|
167
|
+
"token_prefix",
|
|
168
|
+
"unexpected_bundle_entries",
|
|
169
|
+
"utc_now",
|
|
170
|
+
"validate_metadata_payload",
|
|
171
|
+
"validate_upload_endpoint",
|
|
172
|
+
"write_setup_env_file",
|
|
173
|
+
]
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Cloud bundle schema and safety metadata."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from .errors import CloudBundleError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
BUNDLE_SCHEMA = "code_mower.cloudBenchmarkBundle.v1"
|
|
12
|
+
BUNDLE_MANIFEST_FILENAME = "code-mower-cloud-bundle.json"
|
|
13
|
+
MAX_REPORT_UPLOAD_BYTES = 1_000_000
|
|
14
|
+
MAX_EVENT_COUNT = 500
|
|
15
|
+
MAX_METADATA_DEPTH = 32
|
|
16
|
+
SAFE_REPORT_KINDS = {
|
|
17
|
+
"authoring-runs",
|
|
18
|
+
"calibration-runs",
|
|
19
|
+
"lane-policy",
|
|
20
|
+
"reviewer-metrics",
|
|
21
|
+
"spend",
|
|
22
|
+
"value-report",
|
|
23
|
+
}
|
|
24
|
+
SAFE_EVENT_TYPES = {
|
|
25
|
+
"calibration_run",
|
|
26
|
+
"dogfood_upload",
|
|
27
|
+
"lane_policy_snapshot",
|
|
28
|
+
"reviewer_run",
|
|
29
|
+
"value_report_snapshot",
|
|
30
|
+
"workflow_run",
|
|
31
|
+
}
|
|
32
|
+
EXCLUDED_CONTENT = (
|
|
33
|
+
"source_code",
|
|
34
|
+
"raw_diffs",
|
|
35
|
+
"raw_model_transcripts",
|
|
36
|
+
"raw_stdout_stderr",
|
|
37
|
+
"auth_probe_output",
|
|
38
|
+
"secrets",
|
|
39
|
+
)
|
|
40
|
+
EXPECTED_BUNDLE_ENTRIES = {
|
|
41
|
+
"README.md",
|
|
42
|
+
BUNDLE_MANIFEST_FILENAME,
|
|
43
|
+
"reports",
|
|
44
|
+
".README.md.tmp",
|
|
45
|
+
f".{BUNDLE_MANIFEST_FILENAME}.tmp",
|
|
46
|
+
".reports.tmp",
|
|
47
|
+
}
|
|
48
|
+
UNSAFE_METADATA_KEY_FRAGMENTS = (
|
|
49
|
+
"api_key",
|
|
50
|
+
"apikey",
|
|
51
|
+
"auth_output",
|
|
52
|
+
"auth_preview",
|
|
53
|
+
"auth_probe_output",
|
|
54
|
+
"authorization",
|
|
55
|
+
"cookie",
|
|
56
|
+
"output_preview",
|
|
57
|
+
"password",
|
|
58
|
+
"private_key",
|
|
59
|
+
"raw_diff",
|
|
60
|
+
"raw_diffs",
|
|
61
|
+
"raw_model_transcript",
|
|
62
|
+
"raw_model_transcripts",
|
|
63
|
+
"raw_output",
|
|
64
|
+
"raw_stderr",
|
|
65
|
+
"raw_stdout",
|
|
66
|
+
"raw_stdout_stderr",
|
|
67
|
+
"secret",
|
|
68
|
+
"session_id",
|
|
69
|
+
"source_code",
|
|
70
|
+
"stderr",
|
|
71
|
+
"stdout",
|
|
72
|
+
"token_prefix",
|
|
73
|
+
"token_value",
|
|
74
|
+
"transcript",
|
|
75
|
+
)
|
|
76
|
+
UNSAFE_METADATA_VALUE_PATTERNS = tuple(
|
|
77
|
+
re.compile(pattern, re.IGNORECASE)
|
|
78
|
+
for pattern in (
|
|
79
|
+
r"gh[pousr]_[A-Za-z0-9_]{20,}",
|
|
80
|
+
r"github_pat_[A-Za-z0-9_]{20,}",
|
|
81
|
+
r"sk-[A-Za-z0-9_-]{20,}",
|
|
82
|
+
r"cmw_(?:live|test)_[A-Za-z0-9_-]{8,}",
|
|
83
|
+
r"AIza[0-9A-Za-z\-_]{20,}",
|
|
84
|
+
r"-----BEGIN [A-Z ]*PRIVATE KEY-----",
|
|
85
|
+
r"Bearer\s+[A-Za-z0-9._~+/=-]{12,}",
|
|
86
|
+
r"(?:ANTHROPIC_API_KEY|CODE_MOWER_CLOUD_TOKEN|GEMINI_API_KEY|GITHUB_TOKEN|OPENAI_API_KEY)=",
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def is_bundle_manifest(manifest: Any) -> bool:
|
|
92
|
+
return isinstance(manifest, dict) and manifest.get("schema") == BUNDLE_SCHEMA
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _is_unsafe_metadata_key(key: str) -> bool:
|
|
96
|
+
with_word_boundaries = re.sub(r"(?<=[a-z0-9])(?=[A-Z])", "_", key.strip())
|
|
97
|
+
normalized = re.sub(r"[^A-Za-z0-9]+", "_", with_word_boundaries).lower().strip("_")
|
|
98
|
+
if normalized == "token" or normalized.endswith("_token"):
|
|
99
|
+
return True
|
|
100
|
+
return any(fragment in normalized for fragment in UNSAFE_METADATA_KEY_FRAGMENTS)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _unsafe_metadata_value_reason(value: str) -> str:
|
|
104
|
+
for pattern in UNSAFE_METADATA_VALUE_PATTERNS:
|
|
105
|
+
if pattern.search(value):
|
|
106
|
+
return pattern.pattern
|
|
107
|
+
return ""
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def validate_metadata_payload(
|
|
111
|
+
value: Any,
|
|
112
|
+
*,
|
|
113
|
+
path: str = "$",
|
|
114
|
+
depth: int = 0,
|
|
115
|
+
max_depth: int = MAX_METADATA_DEPTH,
|
|
116
|
+
) -> None:
|
|
117
|
+
"""Reject structured cloud metadata that looks like raw output or secrets."""
|
|
118
|
+
|
|
119
|
+
if depth > max_depth:
|
|
120
|
+
raise CloudBundleError(
|
|
121
|
+
"structured cloud metadata is too deeply nested at "
|
|
122
|
+
f"{path!r}; max depth is {max_depth}"
|
|
123
|
+
)
|
|
124
|
+
if isinstance(value, dict):
|
|
125
|
+
for key, item in value.items():
|
|
126
|
+
key_text = str(key)
|
|
127
|
+
child_path = f"{path}.{key_text}"
|
|
128
|
+
if _is_unsafe_metadata_key(key_text):
|
|
129
|
+
raise CloudBundleError(
|
|
130
|
+
"structured cloud metadata contains unsafe field "
|
|
131
|
+
f"{child_path!r}; exclude raw output, auth state, and secrets"
|
|
132
|
+
)
|
|
133
|
+
validate_metadata_payload(
|
|
134
|
+
item,
|
|
135
|
+
path=child_path,
|
|
136
|
+
depth=depth + 1,
|
|
137
|
+
max_depth=max_depth,
|
|
138
|
+
)
|
|
139
|
+
return
|
|
140
|
+
if isinstance(value, list):
|
|
141
|
+
for index, item in enumerate(value):
|
|
142
|
+
validate_metadata_payload(
|
|
143
|
+
item,
|
|
144
|
+
path=f"{path}[{index}]",
|
|
145
|
+
depth=depth + 1,
|
|
146
|
+
max_depth=max_depth,
|
|
147
|
+
)
|
|
148
|
+
return
|
|
149
|
+
if isinstance(value, str):
|
|
150
|
+
reason = _unsafe_metadata_value_reason(value)
|
|
151
|
+
if reason:
|
|
152
|
+
raise CloudBundleError(
|
|
153
|
+
"structured cloud metadata contains a secret-like value at "
|
|
154
|
+
f"{path!r}; matched safety pattern {reason!r}"
|
|
155
|
+
)
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Cloud upload readiness diagnostics."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import shlex
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from .bundle import BUNDLE_MANIFEST_FILENAME
|
|
11
|
+
from .endpoints import (
|
|
12
|
+
dashboard_url_for_endpoint,
|
|
13
|
+
health_url_for_endpoint,
|
|
14
|
+
is_local_http_endpoint,
|
|
15
|
+
probe_cloud_service,
|
|
16
|
+
validate_upload_endpoint,
|
|
17
|
+
)
|
|
18
|
+
from .errors import CloudBundleError
|
|
19
|
+
from .upload import build_upload_payload
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _validate_upload_endpoint(endpoint: str) -> None:
|
|
23
|
+
try:
|
|
24
|
+
validate_upload_endpoint(endpoint)
|
|
25
|
+
except ValueError as exc:
|
|
26
|
+
raise CloudBundleError(str(exc)) from exc
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def run_cloud_doctor(
|
|
30
|
+
*,
|
|
31
|
+
bundle_dir: Path,
|
|
32
|
+
endpoint: str,
|
|
33
|
+
token_env: str,
|
|
34
|
+
require_token: bool = True,
|
|
35
|
+
probe_service: bool = False,
|
|
36
|
+
timeout: float = 5.0,
|
|
37
|
+
) -> dict[str, Any]:
|
|
38
|
+
checks: list[dict[str, Any]] = []
|
|
39
|
+
endpoint_is_valid = False
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
_validate_upload_endpoint(endpoint)
|
|
43
|
+
endpoint_is_valid = True
|
|
44
|
+
checks.append(
|
|
45
|
+
{
|
|
46
|
+
"name": "endpoint",
|
|
47
|
+
"status": "pass",
|
|
48
|
+
"message": f"upload endpoint is allowed: {endpoint}",
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
except CloudBundleError as exc:
|
|
52
|
+
checks.append(
|
|
53
|
+
{
|
|
54
|
+
"name": "endpoint",
|
|
55
|
+
"status": "fail",
|
|
56
|
+
"message": str(exc),
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if probe_service and endpoint_is_valid:
|
|
61
|
+
checks.append(probe_cloud_service(endpoint, timeout=timeout))
|
|
62
|
+
elif endpoint_is_valid:
|
|
63
|
+
checks.append(
|
|
64
|
+
{
|
|
65
|
+
"name": "service",
|
|
66
|
+
"status": "skip",
|
|
67
|
+
"message": "service health probe skipped; pass --probe-service to check it",
|
|
68
|
+
"detail": {"health_url": health_url_for_endpoint(endpoint)},
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
token_present = bool(os.environ.get(token_env, ""))
|
|
73
|
+
if token_present:
|
|
74
|
+
checks.append(
|
|
75
|
+
{
|
|
76
|
+
"name": "token",
|
|
77
|
+
"status": "pass",
|
|
78
|
+
"message": f"{token_env} is set",
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
elif is_local_http_endpoint(endpoint):
|
|
82
|
+
checks.append(
|
|
83
|
+
{
|
|
84
|
+
"name": "token",
|
|
85
|
+
"status": "warn",
|
|
86
|
+
"message": (
|
|
87
|
+
f"{token_env} is not set; local configless ingest may still work"
|
|
88
|
+
),
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
elif not require_token:
|
|
92
|
+
checks.append(
|
|
93
|
+
{
|
|
94
|
+
"name": "token",
|
|
95
|
+
"status": "warn",
|
|
96
|
+
"message": f"{token_env} is not set; upload will remain disabled",
|
|
97
|
+
"remediation": (
|
|
98
|
+
"Run `code-mower cloud setup --token-stdin` or export "
|
|
99
|
+
f"{token_env} before using --yes."
|
|
100
|
+
),
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
else:
|
|
104
|
+
checks.append(
|
|
105
|
+
{
|
|
106
|
+
"name": "token",
|
|
107
|
+
"status": "fail",
|
|
108
|
+
"message": f"{token_env} is not set",
|
|
109
|
+
"remediation": (
|
|
110
|
+
"Set CODE_MOWER_CLOUD_TOKEN to a team ingest token before "
|
|
111
|
+
"running cloud upload --yes."
|
|
112
|
+
),
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
manifest_path = bundle_dir / BUNDLE_MANIFEST_FILENAME
|
|
117
|
+
if manifest_path.is_file():
|
|
118
|
+
try:
|
|
119
|
+
payload = build_upload_payload(bundle_dir=bundle_dir, include_reports=False)
|
|
120
|
+
checks.append(
|
|
121
|
+
{
|
|
122
|
+
"name": "bundle",
|
|
123
|
+
"status": "pass",
|
|
124
|
+
"message": (
|
|
125
|
+
f"bundle is readable with {len(payload['reports'])} report summaries "
|
|
126
|
+
f"and {len(payload['events'])} structured events"
|
|
127
|
+
),
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
except CloudBundleError as exc:
|
|
131
|
+
checks.append(
|
|
132
|
+
{
|
|
133
|
+
"name": "bundle",
|
|
134
|
+
"status": "fail",
|
|
135
|
+
"message": str(exc),
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
else:
|
|
139
|
+
checks.append(
|
|
140
|
+
{
|
|
141
|
+
"name": "bundle",
|
|
142
|
+
"status": "warn",
|
|
143
|
+
"message": (
|
|
144
|
+
f"bundle manifest not found at {manifest_path}; run cloud export first"
|
|
145
|
+
),
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
failures = sum(1 for check in checks if check["status"] == "fail")
|
|
150
|
+
warnings = sum(1 for check in checks if check["status"] == "warn")
|
|
151
|
+
return {
|
|
152
|
+
"mode": "cloud-doctor",
|
|
153
|
+
"status": "fail" if failures else "pass",
|
|
154
|
+
"endpoint": endpoint,
|
|
155
|
+
"dashboard_url": dashboard_url_for_endpoint(endpoint),
|
|
156
|
+
"health_url": health_url_for_endpoint(endpoint),
|
|
157
|
+
"token_env": token_env,
|
|
158
|
+
"bundle_dir": str(bundle_dir),
|
|
159
|
+
"checks": checks,
|
|
160
|
+
"failures": failures,
|
|
161
|
+
"warnings": warnings,
|
|
162
|
+
"next_steps": [
|
|
163
|
+
{
|
|
164
|
+
"id": "open-dashboard",
|
|
165
|
+
"label": "Open the Code Mower Cloud dashboard",
|
|
166
|
+
"url": dashboard_url_for_endpoint(endpoint),
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"id": "setup-token",
|
|
170
|
+
"label": "Store a dashboard-issued token locally",
|
|
171
|
+
"command": (
|
|
172
|
+
"code-mower cloud setup --token-stdin "
|
|
173
|
+
"--team-id YOUR_TEAM_SLUG --install-id YOUR_INSTALL_ID"
|
|
174
|
+
),
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"id": "dry-run-upload",
|
|
178
|
+
"label": "Preview the metadata upload without network transfer",
|
|
179
|
+
"command": f"code-mower cloud upload {shlex.quote(str(bundle_dir))} --dry-run --json",
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"id": "upload",
|
|
183
|
+
"label": "Upload metadata after inspecting the dry run",
|
|
184
|
+
"command": f"code-mower cloud upload {shlex.quote(str(bundle_dir))} --yes --json",
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def render_cloud_doctor_text(report: dict[str, Any]) -> str:
|
|
191
|
+
lines = [
|
|
192
|
+
"Code Mower cloud doctor",
|
|
193
|
+
f"Status: {report['status']}",
|
|
194
|
+
f"Endpoint: {report['endpoint']}",
|
|
195
|
+
f"Dashboard: {report.get('dashboard_url', '')}",
|
|
196
|
+
f"Token env: {report['token_env']}",
|
|
197
|
+
f"Bundle: {report['bundle_dir']}",
|
|
198
|
+
"",
|
|
199
|
+
]
|
|
200
|
+
for check in report["checks"]:
|
|
201
|
+
lines.append(
|
|
202
|
+
f"- {check['status'].upper()} {check['name']}: {check['message']}"
|
|
203
|
+
)
|
|
204
|
+
if check.get("remediation"):
|
|
205
|
+
lines.append(f" remediation: {check['remediation']}")
|
|
206
|
+
return "\n".join(lines) + "\n"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Routine dogfood upload planning helpers.
|
|
2
|
+
|
|
3
|
+
The cloud CLI remains the public API. These helpers keep the repeatable
|
|
4
|
+
dogfood bundle shape separate from network posting and argument parsing.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
DEFAULT_DOGFOOD_REPORTS: tuple[tuple[str, str], ...] = (
|
|
15
|
+
("docs/reviewer-value-report.md", "value-report"),
|
|
16
|
+
("docs/lane-promotion-policy.md", "lane-policy"),
|
|
17
|
+
(".code-mower/reviewer-value-report.md", "value-report"),
|
|
18
|
+
(".code-mower/reviewer-metrics.json", "reviewer-metrics"),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class DogfoodPlan:
|
|
24
|
+
repo_slug: str
|
|
25
|
+
team_id: str
|
|
26
|
+
install_id: str
|
|
27
|
+
source: str
|
|
28
|
+
report_count: int
|
|
29
|
+
extra_event_count: int
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def default_dogfood_reports(repo_path: Path) -> list[tuple[Path, str]]:
|
|
33
|
+
seen: set[Path] = set()
|
|
34
|
+
reports: list[tuple[Path, str]] = []
|
|
35
|
+
for relative_path, kind in DEFAULT_DOGFOOD_REPORTS:
|
|
36
|
+
path = repo_path / relative_path
|
|
37
|
+
if path in seen or not path.is_file():
|
|
38
|
+
continue
|
|
39
|
+
seen.add(path)
|
|
40
|
+
reports.append((path, kind))
|
|
41
|
+
return reports
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def build_dogfood_plan(
|
|
45
|
+
*,
|
|
46
|
+
repo_slug: str,
|
|
47
|
+
team_id: str,
|
|
48
|
+
install_id: str,
|
|
49
|
+
source: str,
|
|
50
|
+
reports: list[tuple[Path, str]],
|
|
51
|
+
events: list[dict[str, Any]],
|
|
52
|
+
) -> DogfoodPlan:
|
|
53
|
+
return DogfoodPlan(
|
|
54
|
+
repo_slug=repo_slug,
|
|
55
|
+
team_id=team_id,
|
|
56
|
+
install_id=install_id,
|
|
57
|
+
source=source,
|
|
58
|
+
report_count=len(reports),
|
|
59
|
+
extra_event_count=len(events),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def build_dogfood_dry_run_preview(
|
|
64
|
+
*,
|
|
65
|
+
endpoint: str,
|
|
66
|
+
payload: dict[str, Any],
|
|
67
|
+
) -> dict[str, Any]:
|
|
68
|
+
return {
|
|
69
|
+
"mode": "cloud-upload-dry-run",
|
|
70
|
+
"endpoint": endpoint,
|
|
71
|
+
"would_upload": False,
|
|
72
|
+
"requires_yes": True,
|
|
73
|
+
"upload_mode": payload["upload_mode"],
|
|
74
|
+
"report_count": len(payload["reports"]),
|
|
75
|
+
"event_count": len(payload["events"]),
|
|
76
|
+
"privacy_mode": payload["privacy_mode"],
|
|
77
|
+
"excluded_content": payload["excluded_content"],
|
|
78
|
+
}
|