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
code_mower/doctor.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Run provider-neutral Code Mower setup and runtime checks."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Sequence
|
|
11
|
+
|
|
12
|
+
if __package__ in {None, ""}:
|
|
13
|
+
module_dir = Path(__file__).resolve().parent
|
|
14
|
+
sys.path.insert(0, str(module_dir.parent))
|
|
15
|
+
if module_dir.name == "code_mower": # pragma: no cover - extracted direct CLI.
|
|
16
|
+
from code_mower import config as code_mower_config
|
|
17
|
+
from code_mower import doctor_checks as _doctor_checks
|
|
18
|
+
from code_mower import package as code_mower_package
|
|
19
|
+
else:
|
|
20
|
+
from tools import code_mower_config, code_mower_package, doctor_checks as _doctor_checks
|
|
21
|
+
elif __package__ == "tools":
|
|
22
|
+
from tools import code_mower_config, code_mower_package, doctor_checks as _doctor_checks
|
|
23
|
+
else: # pragma: no cover - exercised after package extraction.
|
|
24
|
+
from . import config as code_mower_config
|
|
25
|
+
from . import doctor_checks as _doctor_checks
|
|
26
|
+
from . import package as code_mower_package
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
ACTIONS_COST_SAMPLE_DEFAULT = _doctor_checks.ACTIONS_COST_SAMPLE_DEFAULT
|
|
30
|
+
ACTIONS_COST_SAMPLE_MAX = _doctor_checks.ACTIONS_COST_SAMPLE_MAX
|
|
31
|
+
DEFAULT_CLOUD_TOKEN_DIR = _doctor_checks.DEFAULT_CLOUD_TOKEN_DIR
|
|
32
|
+
DEFAULT_CLOUD_TOKEN_ENV = _doctor_checks.DEFAULT_CLOUD_TOKEN_ENV
|
|
33
|
+
STATUS_FAIL = _doctor_checks.STATUS_FAIL
|
|
34
|
+
STATUS_PASS = _doctor_checks.STATUS_PASS
|
|
35
|
+
STATUS_SKIP = _doctor_checks.STATUS_SKIP
|
|
36
|
+
STATUS_WARN = _doctor_checks.STATUS_WARN
|
|
37
|
+
DoctorCheck = _doctor_checks.DoctorCheck
|
|
38
|
+
DoctorReport = _doctor_checks.DoctorReport
|
|
39
|
+
_auth_probe_output_detail = _doctor_checks.auth_probe_output_detail
|
|
40
|
+
_check_cloud_token_surface = _doctor_checks.check_cloud_token_surface
|
|
41
|
+
_evaluate_json_probe = _doctor_checks.evaluate_json_probe
|
|
42
|
+
_local_cli_probe_remediation = _doctor_checks.local_cli_probe_remediation
|
|
43
|
+
render_doctor_text = _doctor_checks.render_doctor_text
|
|
44
|
+
resolve_doctor_config_path = _doctor_checks.resolve_doctor_config_path
|
|
45
|
+
resolve_doctor_config_path_for_script = _doctor_checks.resolve_doctor_config_path_for_script
|
|
46
|
+
resolve_doctor_provider_templates_path = _doctor_checks.resolve_doctor_provider_templates_path
|
|
47
|
+
run_doctor = _doctor_checks.run_doctor
|
|
48
|
+
_token_file_mentions_cloud_token = _doctor_checks.token_file_mentions_cloud_token
|
|
49
|
+
_apply_first_run_defaults = _doctor_checks.apply_first_run_defaults
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
_DOCTOR_COMPAT_EXPORTS = (
|
|
53
|
+
DEFAULT_CLOUD_TOKEN_DIR,
|
|
54
|
+
DEFAULT_CLOUD_TOKEN_ENV,
|
|
55
|
+
STATUS_FAIL,
|
|
56
|
+
STATUS_PASS,
|
|
57
|
+
STATUS_SKIP,
|
|
58
|
+
STATUS_WARN,
|
|
59
|
+
DoctorCheck,
|
|
60
|
+
DoctorReport,
|
|
61
|
+
_auth_probe_output_detail,
|
|
62
|
+
_check_cloud_token_surface,
|
|
63
|
+
_evaluate_json_probe,
|
|
64
|
+
_apply_first_run_defaults,
|
|
65
|
+
_local_cli_probe_remediation,
|
|
66
|
+
resolve_doctor_config_path_for_script,
|
|
67
|
+
resolve_doctor_provider_templates_path,
|
|
68
|
+
_token_file_mentions_cloud_token,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
73
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
74
|
+
parser.add_argument("config", nargs="?", default="code-mower.yml")
|
|
75
|
+
parser.add_argument(
|
|
76
|
+
"--provider-templates",
|
|
77
|
+
default=code_mower_package.DEFAULT_PROVIDER_TEMPLATES,
|
|
78
|
+
)
|
|
79
|
+
parser.add_argument("--profile", default=None)
|
|
80
|
+
parser.add_argument(
|
|
81
|
+
"--easy",
|
|
82
|
+
action="store_true",
|
|
83
|
+
help=(
|
|
84
|
+
"first-run alias for --profile recommended; if code-mower.yml is "
|
|
85
|
+
"absent, use the packaged example config"
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
parser.add_argument(
|
|
89
|
+
"--v05",
|
|
90
|
+
action="store_true",
|
|
91
|
+
help=(
|
|
92
|
+
"v0.5 early-adopter preset: --easy --profile recommended "
|
|
93
|
+
"--probe-runtime --github --cloud"
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
parser.add_argument(
|
|
97
|
+
"--preflight",
|
|
98
|
+
action="store_true",
|
|
99
|
+
help=(
|
|
100
|
+
"friendly alias for the v0.5 first-run preset: --easy "
|
|
101
|
+
"--profile recommended --probe-runtime --github --cloud"
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
parser.add_argument("--probe-runtime", action="store_true")
|
|
105
|
+
parser.add_argument(
|
|
106
|
+
"--github",
|
|
107
|
+
action="store_true",
|
|
108
|
+
help="inspect GitHub repo visibility, branch protection, and provider setup hints",
|
|
109
|
+
)
|
|
110
|
+
parser.add_argument(
|
|
111
|
+
"--cloud",
|
|
112
|
+
action="store_true",
|
|
113
|
+
help="check optional Code Mower Cloud token setup without reading or printing token values",
|
|
114
|
+
)
|
|
115
|
+
parser.add_argument("--http-timeout", type=int, default=5)
|
|
116
|
+
parser.add_argument(
|
|
117
|
+
"--actions-cost-sample",
|
|
118
|
+
type=int,
|
|
119
|
+
default=ACTIONS_COST_SAMPLE_DEFAULT,
|
|
120
|
+
help=(
|
|
121
|
+
"number of recent Actions runs to sample for private-repo cost "
|
|
122
|
+
f"diagnostics, capped at {ACTIONS_COST_SAMPLE_MAX}"
|
|
123
|
+
),
|
|
124
|
+
)
|
|
125
|
+
parser.add_argument("--strict", action="store_true")
|
|
126
|
+
parser.add_argument("--json", action="store_true")
|
|
127
|
+
args = parser.parse_args(argv)
|
|
128
|
+
|
|
129
|
+
_apply_first_run_defaults(args)
|
|
130
|
+
if args.easy and args.profile is None:
|
|
131
|
+
args.profile = "recommended"
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
provider_templates_path = resolve_doctor_provider_templates_path(args.provider_templates)
|
|
135
|
+
report = run_doctor(
|
|
136
|
+
config_path=resolve_doctor_config_path(args.config, easy=args.easy),
|
|
137
|
+
provider_templates_path=provider_templates_path,
|
|
138
|
+
profile=args.profile,
|
|
139
|
+
probe_runtime=args.probe_runtime,
|
|
140
|
+
github=args.github,
|
|
141
|
+
cloud=args.cloud,
|
|
142
|
+
http_timeout=args.http_timeout,
|
|
143
|
+
actions_cost_sample=args.actions_cost_sample,
|
|
144
|
+
)
|
|
145
|
+
except code_mower_config.ConfigError as exc:
|
|
146
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
147
|
+
return 1
|
|
148
|
+
|
|
149
|
+
if args.json:
|
|
150
|
+
print(json.dumps(report.as_dict(), indent=2, sort_keys=True))
|
|
151
|
+
else:
|
|
152
|
+
print(render_doctor_text(report), end="")
|
|
153
|
+
if report.failures:
|
|
154
|
+
return 1
|
|
155
|
+
if args.strict and report.warnings:
|
|
156
|
+
return 1
|
|
157
|
+
return 0
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
if __name__ == "__main__":
|
|
161
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Doctor check registry primitives.
|
|
2
|
+
|
|
3
|
+
The public CLI remains ``code-mower doctor``. This package keeps the
|
|
4
|
+
check/report data model independent from the large command adapter so runtime,
|
|
5
|
+
GitHub, provider, cloud, and output checks can move behind registries without
|
|
6
|
+
changing the CLI contract.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .cloud import (
|
|
10
|
+
DEFAULT_CLOUD_TOKEN_DIR,
|
|
11
|
+
DEFAULT_CLOUD_TOKEN_ENV,
|
|
12
|
+
check_cloud_token_surface,
|
|
13
|
+
token_file_mentions_cloud_token,
|
|
14
|
+
)
|
|
15
|
+
from .common import (
|
|
16
|
+
ACTIONS_COST_SAMPLE_DEFAULT,
|
|
17
|
+
ACTIONS_COST_SAMPLE_MAX,
|
|
18
|
+
load_inputs,
|
|
19
|
+
)
|
|
20
|
+
from .github import check_github_setup
|
|
21
|
+
from .groups import GROUP_LABELS, doctor_check_group_id
|
|
22
|
+
from .models import (
|
|
23
|
+
STATUS_FAIL,
|
|
24
|
+
STATUS_PASS,
|
|
25
|
+
STATUS_SKIP,
|
|
26
|
+
STATUS_WARN,
|
|
27
|
+
DoctorCheck,
|
|
28
|
+
DoctorReport,
|
|
29
|
+
)
|
|
30
|
+
from .output import doctor_output_group, render_doctor_text
|
|
31
|
+
from .presets import (
|
|
32
|
+
apply_first_run_defaults,
|
|
33
|
+
resolve_doctor_config_path,
|
|
34
|
+
resolve_doctor_config_path_for_script,
|
|
35
|
+
resolve_doctor_provider_templates_path,
|
|
36
|
+
)
|
|
37
|
+
from .providers import (
|
|
38
|
+
check_lane_runtime,
|
|
39
|
+
effective_lane,
|
|
40
|
+
provider_template_coverage,
|
|
41
|
+
selected_lanes,
|
|
42
|
+
)
|
|
43
|
+
from .provider_probe import evaluate_json_probe, local_cli_probe_remediation
|
|
44
|
+
from .privacy import auth_probe_output_detail
|
|
45
|
+
from .registry import (
|
|
46
|
+
DEFAULT_CHECK_GROUPS,
|
|
47
|
+
BASE_DOCTOR_STAGES,
|
|
48
|
+
OPTIONAL_DOCTOR_STAGES,
|
|
49
|
+
DoctorCheckGroup,
|
|
50
|
+
DoctorCheckStage,
|
|
51
|
+
build_doctor_run_plan,
|
|
52
|
+
default_check_group_ids,
|
|
53
|
+
)
|
|
54
|
+
from .runtime import (
|
|
55
|
+
check_github_auth_surface,
|
|
56
|
+
check_pytest,
|
|
57
|
+
check_python_runtime,
|
|
58
|
+
check_ripgrep,
|
|
59
|
+
)
|
|
60
|
+
from .runner import run_doctor
|
|
61
|
+
|
|
62
|
+
__all__ = [
|
|
63
|
+
"DEFAULT_CHECK_GROUPS",
|
|
64
|
+
"GROUP_LABELS",
|
|
65
|
+
"BASE_DOCTOR_STAGES",
|
|
66
|
+
"OPTIONAL_DOCTOR_STAGES",
|
|
67
|
+
"ACTIONS_COST_SAMPLE_DEFAULT",
|
|
68
|
+
"ACTIONS_COST_SAMPLE_MAX",
|
|
69
|
+
"DEFAULT_CLOUD_TOKEN_DIR",
|
|
70
|
+
"DEFAULT_CLOUD_TOKEN_ENV",
|
|
71
|
+
"DoctorCheck",
|
|
72
|
+
"DoctorCheckGroup",
|
|
73
|
+
"DoctorCheckStage",
|
|
74
|
+
"DoctorReport",
|
|
75
|
+
"STATUS_FAIL",
|
|
76
|
+
"STATUS_PASS",
|
|
77
|
+
"STATUS_SKIP",
|
|
78
|
+
"STATUS_WARN",
|
|
79
|
+
"apply_first_run_defaults",
|
|
80
|
+
"auth_probe_output_detail",
|
|
81
|
+
"build_doctor_run_plan",
|
|
82
|
+
"check_cloud_token_surface",
|
|
83
|
+
"check_github_auth_surface",
|
|
84
|
+
"check_github_setup",
|
|
85
|
+
"check_lane_runtime",
|
|
86
|
+
"check_pytest",
|
|
87
|
+
"check_python_runtime",
|
|
88
|
+
"check_ripgrep",
|
|
89
|
+
"default_check_group_ids",
|
|
90
|
+
"doctor_check_group_id",
|
|
91
|
+
"doctor_output_group",
|
|
92
|
+
"effective_lane",
|
|
93
|
+
"evaluate_json_probe",
|
|
94
|
+
"load_inputs",
|
|
95
|
+
"local_cli_probe_remediation",
|
|
96
|
+
"provider_template_coverage",
|
|
97
|
+
"render_doctor_text",
|
|
98
|
+
"resolve_doctor_config_path",
|
|
99
|
+
"resolve_doctor_config_path_for_script",
|
|
100
|
+
"resolve_doctor_provider_templates_path",
|
|
101
|
+
"run_doctor",
|
|
102
|
+
"selected_lanes",
|
|
103
|
+
"token_file_mentions_cloud_token",
|
|
104
|
+
]
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Code Mower Cloud doctor checks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import stat
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .models import STATUS_PASS, STATUS_SKIP, STATUS_WARN, DoctorCheck
|
|
10
|
+
|
|
11
|
+
DEFAULT_CLOUD_TOKEN_ENV = "CODE_MOWER_CLOUD_TOKEN"
|
|
12
|
+
DEFAULT_CLOUD_TOKEN_DIR = Path("~/.config/code-mower/tokens")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def token_file_mentions_cloud_token(path: Path, token_env: str) -> bool | None:
|
|
16
|
+
try:
|
|
17
|
+
text = path.read_text(encoding="utf-8")
|
|
18
|
+
except (OSError, UnicodeDecodeError):
|
|
19
|
+
return None
|
|
20
|
+
for line in text.splitlines():
|
|
21
|
+
stripped = line.strip()
|
|
22
|
+
if not stripped or stripped.startswith("#"):
|
|
23
|
+
continue
|
|
24
|
+
if stripped.startswith("export "):
|
|
25
|
+
stripped = stripped.removeprefix("export ").strip()
|
|
26
|
+
if stripped.startswith(f"{token_env}="):
|
|
27
|
+
return True
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def check_cloud_token_surface(
|
|
32
|
+
*,
|
|
33
|
+
token_env: str = DEFAULT_CLOUD_TOKEN_ENV,
|
|
34
|
+
token_dir: Path | None = None,
|
|
35
|
+
) -> DoctorCheck:
|
|
36
|
+
"""Check optional Code Mower Cloud token setup without exposing secrets."""
|
|
37
|
+
|
|
38
|
+
if os.environ.get(token_env):
|
|
39
|
+
return DoctorCheck(
|
|
40
|
+
name="cloud.token",
|
|
41
|
+
status=STATUS_PASS,
|
|
42
|
+
message="Code Mower Cloud token env is set",
|
|
43
|
+
detail={"token_env": token_env, "source": "env"},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
directory = (token_dir or DEFAULT_CLOUD_TOKEN_DIR).expanduser()
|
|
47
|
+
redacted_dir = "~/.config/code-mower/tokens"
|
|
48
|
+
if not directory.exists():
|
|
49
|
+
return DoctorCheck(
|
|
50
|
+
name="cloud.token",
|
|
51
|
+
status=STATUS_SKIP,
|
|
52
|
+
message="optional Code Mower Cloud token is not configured",
|
|
53
|
+
detail={"token_env": token_env, "token_dir": redacted_dir},
|
|
54
|
+
remediation=(
|
|
55
|
+
"Cloud upload is optional. To enable it, create or receive a "
|
|
56
|
+
"developer/team token, then run `code-mower cloud setup "
|
|
57
|
+
"--token-stdin` and source the generated env file."
|
|
58
|
+
),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
token_files: list[str] = []
|
|
62
|
+
unreadable_files: list[str] = []
|
|
63
|
+
insecure_files: list[str] = []
|
|
64
|
+
for path in sorted(directory.glob("*.env")):
|
|
65
|
+
has_token = token_file_mentions_cloud_token(path, token_env)
|
|
66
|
+
if has_token is None:
|
|
67
|
+
unreadable_files.append(path.name)
|
|
68
|
+
continue
|
|
69
|
+
if not has_token:
|
|
70
|
+
continue
|
|
71
|
+
token_files.append(path.name)
|
|
72
|
+
try:
|
|
73
|
+
mode = stat.S_IMODE(path.stat().st_mode)
|
|
74
|
+
except OSError:
|
|
75
|
+
unreadable_files.append(path.name)
|
|
76
|
+
continue
|
|
77
|
+
if mode & 0o077:
|
|
78
|
+
insecure_files.append(path.name)
|
|
79
|
+
|
|
80
|
+
if unreadable_files:
|
|
81
|
+
return DoctorCheck(
|
|
82
|
+
name="cloud.token",
|
|
83
|
+
status=STATUS_WARN,
|
|
84
|
+
message="could not inspect one or more Code Mower Cloud token files",
|
|
85
|
+
detail={
|
|
86
|
+
"token_env": token_env,
|
|
87
|
+
"token_dir": redacted_dir,
|
|
88
|
+
"unreadable_file_count": len(unreadable_files),
|
|
89
|
+
"unreadable_files": unreadable_files,
|
|
90
|
+
},
|
|
91
|
+
remediation=(
|
|
92
|
+
"Verify token files under ~/.config/code-mower/tokens are UTF-8 "
|
|
93
|
+
"env files owned by the current user."
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if not token_files:
|
|
98
|
+
return DoctorCheck(
|
|
99
|
+
name="cloud.token",
|
|
100
|
+
status=STATUS_SKIP,
|
|
101
|
+
message="optional Code Mower Cloud token file was not found",
|
|
102
|
+
detail={"token_env": token_env, "token_dir": redacted_dir},
|
|
103
|
+
remediation=(
|
|
104
|
+
"Cloud upload is optional. To enable it, run `code-mower cloud "
|
|
105
|
+
"setup --token-stdin` and source the generated env file."
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
detail = {
|
|
110
|
+
"token_env": token_env,
|
|
111
|
+
"token_dir": redacted_dir,
|
|
112
|
+
"token_file_count": len(token_files),
|
|
113
|
+
"token_files": token_files,
|
|
114
|
+
}
|
|
115
|
+
if insecure_files:
|
|
116
|
+
return DoctorCheck(
|
|
117
|
+
name="cloud.token",
|
|
118
|
+
status=STATUS_WARN,
|
|
119
|
+
message="Code Mower Cloud token file permissions are too broad",
|
|
120
|
+
detail={**detail, "insecure_files": insecure_files},
|
|
121
|
+
remediation="Run `chmod 600 ~/.config/code-mower/tokens/*.env`.",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
return DoctorCheck(
|
|
125
|
+
name="cloud.token",
|
|
126
|
+
status=STATUS_PASS,
|
|
127
|
+
message="Code Mower Cloud token file is configured",
|
|
128
|
+
detail=detail,
|
|
129
|
+
)
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""Shared doctor helper functions and dependency shims."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Mapping, Sequence
|
|
8
|
+
|
|
9
|
+
from .models import STATUS_FAIL, STATUS_PASS, STATUS_SKIP, STATUS_WARN, DoctorCheck
|
|
10
|
+
|
|
11
|
+
if __package__ in {None, ""}:
|
|
12
|
+
module_dir = Path(__file__).resolve().parent
|
|
13
|
+
sys.path.insert(0, str(module_dir.parents[1]))
|
|
14
|
+
if module_dir.parent.name == "code_mower": # pragma: no cover - direct package path.
|
|
15
|
+
from code_mower import config as code_mower_config
|
|
16
|
+
from code_mower import local_llm_profiles
|
|
17
|
+
from code_mower import package as code_mower_package
|
|
18
|
+
from code_mower import secrets as code_mower_secrets
|
|
19
|
+
else: # pragma: no cover - extracted direct CLI path.
|
|
20
|
+
from tools import (
|
|
21
|
+
code_mower_config,
|
|
22
|
+
code_mower_package,
|
|
23
|
+
code_mower_secrets,
|
|
24
|
+
local_llm_profiles,
|
|
25
|
+
)
|
|
26
|
+
elif __package__.startswith("tools"):
|
|
27
|
+
from .. import (
|
|
28
|
+
code_mower_config,
|
|
29
|
+
code_mower_package,
|
|
30
|
+
code_mower_secrets,
|
|
31
|
+
local_llm_profiles,
|
|
32
|
+
)
|
|
33
|
+
else: # pragma: no cover - exercised after package extraction.
|
|
34
|
+
from .. import config as code_mower_config
|
|
35
|
+
from .. import local_llm_profiles
|
|
36
|
+
from .. import package as code_mower_package
|
|
37
|
+
from .. import secrets as code_mower_secrets
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
SUPPORTED_TOKEN_FILE_ENV_NAMES = frozenset({"GEMINI_API_KEY", "GOOGLE_API_KEY"})
|
|
41
|
+
TRUTHY_ENV_VALUES = frozenset({"1", "true", "yes", "on"})
|
|
42
|
+
ACTIONS_BILLING_BLOCK_PATTERNS = (
|
|
43
|
+
"recent account payments have failed",
|
|
44
|
+
"spending limit needs to be increased",
|
|
45
|
+
)
|
|
46
|
+
ACTIONS_INSPECTABLE_FAILURE_CONCLUSIONS = frozenset(
|
|
47
|
+
{"failure", "action_required", "timed_out", "startup_failure"}
|
|
48
|
+
)
|
|
49
|
+
MAX_ACTIONS_FAILED_RUNS_TO_INSPECT = 5
|
|
50
|
+
MAX_ACTIONS_FAILED_JOBS_TO_INSPECT = 20
|
|
51
|
+
ACTIONS_COST_SAMPLE_DEFAULT = 100
|
|
52
|
+
ACTIONS_COST_SAMPLE_MAX = 100
|
|
53
|
+
ACTIONS_METADATA_WORKFLOW_MARKERS = (
|
|
54
|
+
"audit-labeler",
|
|
55
|
+
"labeler",
|
|
56
|
+
"clear-stale",
|
|
57
|
+
"audit-label-cleanup",
|
|
58
|
+
"devin-audit-bridge",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
__all__ = [
|
|
62
|
+
"ACTIONS_BILLING_BLOCK_PATTERNS",
|
|
63
|
+
"ACTIONS_COST_SAMPLE_DEFAULT",
|
|
64
|
+
"ACTIONS_COST_SAMPLE_MAX",
|
|
65
|
+
"ACTIONS_INSPECTABLE_FAILURE_CONCLUSIONS",
|
|
66
|
+
"ACTIONS_METADATA_WORKFLOW_MARKERS",
|
|
67
|
+
"DoctorCheck",
|
|
68
|
+
"MAX_ACTIONS_FAILED_JOBS_TO_INSPECT",
|
|
69
|
+
"MAX_ACTIONS_FAILED_RUNS_TO_INSPECT",
|
|
70
|
+
"STATUS_FAIL",
|
|
71
|
+
"STATUS_PASS",
|
|
72
|
+
"STATUS_SKIP",
|
|
73
|
+
"STATUS_WARN",
|
|
74
|
+
"SUPPORTED_TOKEN_FILE_ENV_NAMES",
|
|
75
|
+
"TRUTHY_ENV_VALUES",
|
|
76
|
+
"as_mapping",
|
|
77
|
+
"as_sequence",
|
|
78
|
+
"code_mower_config",
|
|
79
|
+
"code_mower_package",
|
|
80
|
+
"code_mower_secrets",
|
|
81
|
+
"join_names",
|
|
82
|
+
"load_inputs",
|
|
83
|
+
"local_cli_remediation",
|
|
84
|
+
"local_llm_profiles",
|
|
85
|
+
"token_remediation",
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def as_mapping(value: Any, name: str) -> Mapping[str, Any]:
|
|
90
|
+
if isinstance(value, Mapping):
|
|
91
|
+
return value
|
|
92
|
+
raise code_mower_config.ConfigError(f"{name} must be a mapping")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def as_sequence(value: Any) -> tuple[Any, ...]:
|
|
96
|
+
if isinstance(value, (list, tuple)):
|
|
97
|
+
return tuple(value)
|
|
98
|
+
return ()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def join_names(names: Sequence[str]) -> str:
|
|
102
|
+
return ", ".join(str(name) for name in names)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def token_remediation(
|
|
106
|
+
missing: Sequence[str],
|
|
107
|
+
missing_any: Sequence[Sequence[str]],
|
|
108
|
+
) -> str:
|
|
109
|
+
parts: list[str] = []
|
|
110
|
+
if missing:
|
|
111
|
+
parts.append(
|
|
112
|
+
"set "
|
|
113
|
+
+ join_names(missing)
|
|
114
|
+
+ " in your shell or GitHub secret store before enabling this lane"
|
|
115
|
+
)
|
|
116
|
+
for group in missing_any:
|
|
117
|
+
names = [str(name) for name in group]
|
|
118
|
+
if not names:
|
|
119
|
+
continue
|
|
120
|
+
if set(names) & {"GEMINI_API_KEY", "GOOGLE_API_KEY"}:
|
|
121
|
+
parts.append(
|
|
122
|
+
"set GEMINI_API_KEY or GOOGLE_API_KEY, or run "
|
|
123
|
+
"`code-mower init auth gemini` and export GEMINI_API_KEY_FILE"
|
|
124
|
+
)
|
|
125
|
+
else:
|
|
126
|
+
parts.append("set one of " + join_names(names))
|
|
127
|
+
if not parts:
|
|
128
|
+
return ""
|
|
129
|
+
return "; ".join(parts) + "."
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def local_cli_remediation(commands: Sequence[str], command_env: str = "") -> str:
|
|
133
|
+
command_text = " or ".join(str(command) for command in commands if command)
|
|
134
|
+
if not command_text:
|
|
135
|
+
return (
|
|
136
|
+
"Configure provider_config.command or a non-empty provider id for "
|
|
137
|
+
"this lane, then rerun doctor."
|
|
138
|
+
)
|
|
139
|
+
if command_env:
|
|
140
|
+
return (
|
|
141
|
+
f"Install {command_text}, or set {command_env} to the absolute path "
|
|
142
|
+
"of the provider CLI, then rerun doctor."
|
|
143
|
+
)
|
|
144
|
+
return f"Install {command_text} and ensure it is on PATH, then rerun doctor."
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def load_inputs(
|
|
148
|
+
config_path: Path,
|
|
149
|
+
provider_templates_path: Path,
|
|
150
|
+
) -> tuple[Mapping[str, Any] | None, Mapping[str, Any] | None, list[DoctorCheck]]:
|
|
151
|
+
checks: list[DoctorCheck] = []
|
|
152
|
+
config: Mapping[str, Any] | None = None
|
|
153
|
+
templates: Mapping[str, Any] | None = None
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
config = code_mower_config.load_config(config_path)
|
|
157
|
+
issues = code_mower_config.validate_config(config)
|
|
158
|
+
except (OSError, code_mower_config.ConfigError) as exc:
|
|
159
|
+
checks.append(
|
|
160
|
+
DoctorCheck(
|
|
161
|
+
name="config.load",
|
|
162
|
+
status=STATUS_FAIL,
|
|
163
|
+
message=f"cannot load config: {exc}",
|
|
164
|
+
remediation=(
|
|
165
|
+
"Run `code-mower init --easy` to render a starter config, "
|
|
166
|
+
"or pass an existing config path to doctor."
|
|
167
|
+
),
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
else:
|
|
171
|
+
if issues:
|
|
172
|
+
checks.append(
|
|
173
|
+
DoctorCheck(
|
|
174
|
+
name="config.validate",
|
|
175
|
+
status=STATUS_FAIL,
|
|
176
|
+
message=code_mower_config._format_issues(issues),
|
|
177
|
+
remediation=(
|
|
178
|
+
"Fix the listed config issues or regenerate the starter "
|
|
179
|
+
"setup with `code-mower init --easy`."
|
|
180
|
+
),
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
checks.append(
|
|
185
|
+
DoctorCheck(
|
|
186
|
+
name="config.validate",
|
|
187
|
+
status=STATUS_PASS,
|
|
188
|
+
message="config validates",
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
templates = code_mower_package.load_provider_templates(provider_templates_path)
|
|
194
|
+
except (OSError, code_mower_config.ConfigError) as exc:
|
|
195
|
+
checks.append(
|
|
196
|
+
DoctorCheck(
|
|
197
|
+
name="provider_templates.load",
|
|
198
|
+
status=STATUS_FAIL,
|
|
199
|
+
message=f"cannot load provider templates: {exc}",
|
|
200
|
+
remediation=(
|
|
201
|
+
"Run from the repository root, install the packaged templates, "
|
|
202
|
+
"or pass --provider-templates with a readable catalog path."
|
|
203
|
+
),
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
else:
|
|
207
|
+
checks.append(
|
|
208
|
+
DoctorCheck(
|
|
209
|
+
name="provider_templates.load",
|
|
210
|
+
status=STATUS_PASS,
|
|
211
|
+
message="provider templates load",
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return config, templates, checks
|