code-mower 0.5.0b45__tar.gz → 0.5.0b47__tar.gz
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-0.5.0b45 → code_mower-0.5.0b47}/PKG-INFO +6 -6
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/README.md +5 -5
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/pyproject.toml +1 -1
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/__init__.py +1 -1
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/audit_labeler_lib.py +46 -6
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/claude_audit_pr.py +175 -4
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/cli.py +1 -1
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/code_mower_telemetry.py +2 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/codex_audit_pr.py +56 -13
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/__init__.py +2 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/github_actions_failures.py +26 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/runner.py +2 -0
- code_mower-0.5.0b47/src/code_mower/doctor_checks/runtime.py +173 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/gate_health.py +399 -20
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/init.py +51 -2
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/package_content.py +3 -4
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/package_manifest.py +1 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/provider_runners/__init__.py +8 -1
- code_mower-0.5.0b47/src/code_mower/provider_runners/comments.py +71 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/provider_runners/github_pr.py +15 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/provider_runners/verdict_artifacts.py +12 -4
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/product-support/safe_gh_comment.py +21 -1
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/workflows/builder-provenance.yml.j2 +1 -1
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/workflows/code-mower-gate-health.yml.j2 +7 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47/src/code_mower}/templates/workflows/code-mower-gate.yml.j2 +112 -11
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/workflows/needs-owner-notify.yml.j2 +3 -3
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/workflows/review-clear-stale.yml.j2 +3 -4
- {code_mower-0.5.0b45 → code_mower-0.5.0b47/src/code_mower}/templates/workflows/self-hosted-local-audit.yml.j2 +6 -2
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/workflows/trailer-comment-labeler.yml.j2 +15 -2
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/trailer_comment_labeler.py +2 -1
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower.egg-info/PKG-INFO +6 -6
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower.egg-info/SOURCES.txt +1 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/workflows/code-mower-gate-health.yml.j2 +7 -0
- {code_mower-0.5.0b45/src/code_mower → code_mower-0.5.0b47}/templates/workflows/code-mower-gate.yml.j2 +112 -11
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/workflows/review-clear-stale.yml.j2 +3 -4
- {code_mower-0.5.0b45/src/code_mower → code_mower-0.5.0b47}/templates/workflows/self-hosted-local-audit.yml.j2 +6 -2
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/workflows/trailer-comment-labeler.yml.j2 +15 -2
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_audit_comment_posture.py +31 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_builder_identity.py +21 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_claude_audit_pr.py +109 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_codex_audit_pr.py +57 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_doctor_github_checks.py +30 -0
- code_mower-0.5.0b47/tests/test_doctor_runtime.py +60 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_gate_health.py +462 -3
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_release_hygiene.py +592 -52
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_reviewer_metrics_core.py +60 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_trailer_comment_labeler.py +75 -5
- code_mower-0.5.0b45/src/code_mower/doctor_checks/runtime.py +0 -89
- code_mower-0.5.0b45/src/code_mower/provider_runners/comments.py +0 -31
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/CODE_OF_CONDUCT.md +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/LICENSE +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/MANIFEST.in +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/NOTICE +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/SUPPORT.md +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/requirements/requirements.txt +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/setup.cfg +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/adapters/__init__.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/adapters/_base.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/adapters/cursor_bugbot.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/adapters/gitar.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/adapters/greptile.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/adapters/qodo.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/antigravity_cli_audit_pr.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/audit_handoff_log.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/audit_progress.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/blind_review_artifacts.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/blind_review_coordinator.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/bootstrap.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/builder_experiment.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/builder_runs.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/__init__.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/arms.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/auto_discovery.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/commands.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/context_inputs.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/corpus.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/effect_report.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/evidence.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/evidence_report.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/identity.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/metrics.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/overlap.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/planning.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/policy.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/results.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/run_results.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/run_status.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/runner.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/truth.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/calibration/value_report.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/checks.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/claude_cli_bounce.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/claude_cli_environment.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/clear_stale.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/cloud.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/cloud_client/__init__.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/cloud_client/bundle.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/cloud_client/doctor.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/cloud_client/dogfood.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/cloud_client/endpoints.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/cloud_client/errors.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/cloud_client/events.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/cloud_client/export.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/cloud_client/git_metadata.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/cloud_client/manifest.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/cloud_client/operations.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/cloud_client/reports.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/cloud_client/setup.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/cloud_client/upload.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/code_mower_calibration.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/code_mower_context_packs.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/code_mower_merge.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/coderabbit_cli_audit_pr.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/codex_audit_env_preflight.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/codex_audit_schema_smoke.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/codex_audit_verdict.schema.json +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/config.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/cloud.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/common.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/github.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/github_actions.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/github_actions_cost.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/github_actions_cost_summary.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/github_actions_failure_annotations.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/github_actions_failure_models.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/github_actions_failure_scan.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/github_actions_failure_selection.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/github_actions_permissions.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/github_api.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/github_branch.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/github_config.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/github_provider.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/github_repo.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/groups.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/models.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/output.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/presets.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/privacy.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/provider_api_model.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/provider_api_model_openai.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/provider_api_model_profiles.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/provider_env.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/provider_env_required.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/provider_env_tokens.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/provider_local_cli.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/provider_local_cli_commands.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/provider_local_cli_probe_config.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/provider_probe.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/provider_probe_auth.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/provider_probe_evaluation.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/provider_probe_json.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/provider_probe_remediation.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/provider_review_hygiene.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/providers.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/registry.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/doctor_checks/runtime_github_auth.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/gemini_cli_audit_pr.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/grok_build_audit_pr.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/hermes_cli_audit_pr.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/lane_configs/__init__.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/lane_configs/aider.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/lane_configs/antigravity_cli.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/lane_configs/claude.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/lane_configs/codex.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/lane_configs/devin.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/lane_configs/gemini_cli.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/lane_configs/grok_build.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/lane_configs/hermes_cli.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/lane_configs/local_llm.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/local_llm_audit_pr.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/local_llm_bakeoff.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/local_llm_calibration.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/local_llm_profiles.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/migration.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/migration_install.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/migration_mirror.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/migration_readiness.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/migration_rehearsal.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/next_steps.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/package.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/package_paths.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/package_rendering.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/package_static.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/plan_context.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/prompts.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/provider_registry.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/provider_runners/git.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/provider_runners/github_auth.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/provider_runners/pr_worktree.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/provider_runners/process.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/provider_runners/repo_paths.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/provider_runners/text_schema.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/provider_runners/workspace.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/providers/__init__.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/providers/local_cli.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/providers/provenance.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/release_readiness.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/reviewer_metrics.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/reviewer_spend.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/saas_reviewer_labeler.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/secrets.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/builder-experiment.example.json +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/calibration-corpus.example.json +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/calibration-corpus.json +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/code-mower.example.yml +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/context-packs.example.json +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/lane_prompts/base-audit.md +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/lane_prompts/calibration-policy.md +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/lane_prompts/context-driven-quality.md +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/lane_prompts/docs-design.md +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/lane_prompts/generic-programming.md +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/lane_prompts/operability.md +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/lane_prompts/package-runtime.md +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/lane_prompts/plan-conformance.md +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/lane_prompts/security-threat-model.md +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/product-support/code_mower +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/product-support/code_mower_standalone_pin.env +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/product-support/code_mower_standalone_shadow.sh +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/product-support/run_claude_audit_pr.sh +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/product-support/run_codex_audit_pr.sh +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/product-support/status_report.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/providers.yml +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/reviewer-spend.example.json +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/reviewer-value-report.example.md +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/workflows/audit-label-cleanup.yml.j2 +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/workflows/hosted-bridge.yml.j2 +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/workflows/local-cli-audit.yml.j2 +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/workflows/private-standalone-shadow.yml.j2 +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/workflows/saas-reviewer-labeler.yml.j2 +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/templates/workflows/weekly-status.yml.j2 +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/versioning.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower/work_orders.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower.egg-info/dependency_links.txt +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower.egg-info/entry_points.txt +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower.egg-info/requires.txt +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/src/code_mower.egg-info/top_level.txt +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/builder-experiment.example.json +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/calibration-corpus.example.json +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/calibration-corpus.json +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/code-mower.yml.j2 +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/context-packs.example.json +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/cursor/BUGBOT.md +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/providers/acp_bridge.yml +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/providers/aider.yml +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/providers/antigravity_cli.yml +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/providers/claude_audit.yml +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/providers/claude_review.yml +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/providers/coderabbit_cli.yml +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/providers/codex.yml +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/providers/cursor_bugbot.yml +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/providers/devin.yml +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/providers/gemini_cli.yml +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/providers/gitar.yml +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/providers/greptile.yml +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/providers/hermes_cli.yml +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/providers/local_llm.yml +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/providers/qodo.yml +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/providers.yml +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/reviewer-spend.example.json +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/reviewer-value-report.example.md +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/workflows/audit-label-cleanup.yml.j2 +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/workflows/blind-review-artifacts-dry-run.yml.j2 +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/workflows/builder-provenance.yml.j2 +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/workflows/hosted-bridge.yml.j2 +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/workflows/local-cli-audit.yml.j2 +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/workflows/private-standalone-shadow.yml.j2 +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/templates/workflows/saas-reviewer-labeler.yml.j2 +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_builder_runs.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_calibration_effect_report.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_calibration_truth.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_checks.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_claude_cli_bounce.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_clear_stale.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_cloud_client.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_cloud_git_metadata.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_cloud_manifest.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_cloud_reports.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_coderabbit_cli_audit_pr.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_codex_audit_env_preflight.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_doctor_actions_cost_summary.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_doctor_actions_failure_selection.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_doctor_boundaries.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_doctor_output.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_doctor_privacy.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_doctor_provider_env_required.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_doctor_provider_env_tokens.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_doctor_provider_probe.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_doctor_provider_review_hygiene.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_doctor_registry.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_local_llm_github_helpers.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_plan_context.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_prompts.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_provider_local_cli.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_provider_runners_comments.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_provider_runners_git.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_provider_runners_github_pr.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_provider_runners_pr_worktree.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_provider_runners_process.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_provider_runners_repo_paths.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_provider_runners_text_schema.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_provider_runners_workspace.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_reviewer_spend.py +0 -0
- {code_mower-0.5.0b45 → code_mower-0.5.0b47}/tests/test_work_orders.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-mower
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.0b47
|
|
4
4
|
Summary: Multi-reviewer AI code audit orchestration
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -103,11 +103,11 @@ Code Mower currently targets Python 3.11+; Python 3.12 is recommended.
|
|
|
103
103
|
|
|
104
104
|
```bash
|
|
105
105
|
python3.12 --version
|
|
106
|
-
pipx install --python python3.12 code-mower==0.5.
|
|
106
|
+
pipx install --python python3.12 code-mower==0.5.0b47
|
|
107
107
|
code-mower --version
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
`0.5.
|
|
110
|
+
`0.5.0b47` is a beta release. Until Code Mower publishes a stable `1.0`
|
|
111
111
|
line, use the explicit beta version above or allow prereleases with:
|
|
112
112
|
|
|
113
113
|
```bash
|
|
@@ -155,7 +155,7 @@ First-time command map: [docs/launch-command-surface.md](docs/launch-command-sur
|
|
|
155
155
|
|
|
156
156
|
The current PyPI beta has been rehearsed end-to-end from a clean install:
|
|
157
157
|
[First-User Install Rehearsal](docs/first-user-install-rehearsal.md) records
|
|
158
|
-
the latest 10/10 public-package readiness proof for `code-mower==0.5.
|
|
158
|
+
the latest 10/10 public-package readiness proof for `code-mower==0.5.0b47`.
|
|
159
159
|
|
|
160
160
|
## Optional: Plan Before Coding
|
|
161
161
|
|
|
@@ -310,9 +310,9 @@ builder provenance, start with
|
|
|
310
310
|
|
|
311
311
|
## Installation Status
|
|
312
312
|
|
|
313
|
-
The current public beta is `v0.5.0-beta.
|
|
313
|
+
The current public beta is `v0.5.0-beta.47` from
|
|
314
314
|
[codemower-ai/code-mower](https://github.com/codemower-ai/code-mower), published
|
|
315
|
-
as `code-mower==0.5.
|
|
315
|
+
as `code-mower==0.5.0b47` on [PyPI](https://pypi.org/project/code-mower/).
|
|
316
316
|
GitHub releases remain the auditable source for tags, build artifacts, and
|
|
317
317
|
release notes.
|
|
318
318
|
|
|
@@ -88,11 +88,11 @@ Code Mower currently targets Python 3.11+; Python 3.12 is recommended.
|
|
|
88
88
|
|
|
89
89
|
```bash
|
|
90
90
|
python3.12 --version
|
|
91
|
-
pipx install --python python3.12 code-mower==0.5.
|
|
91
|
+
pipx install --python python3.12 code-mower==0.5.0b47
|
|
92
92
|
code-mower --version
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
`0.5.
|
|
95
|
+
`0.5.0b47` is a beta release. Until Code Mower publishes a stable `1.0`
|
|
96
96
|
line, use the explicit beta version above or allow prereleases with:
|
|
97
97
|
|
|
98
98
|
```bash
|
|
@@ -140,7 +140,7 @@ First-time command map: [docs/launch-command-surface.md](docs/launch-command-sur
|
|
|
140
140
|
|
|
141
141
|
The current PyPI beta has been rehearsed end-to-end from a clean install:
|
|
142
142
|
[First-User Install Rehearsal](docs/first-user-install-rehearsal.md) records
|
|
143
|
-
the latest 10/10 public-package readiness proof for `code-mower==0.5.
|
|
143
|
+
the latest 10/10 public-package readiness proof for `code-mower==0.5.0b47`.
|
|
144
144
|
|
|
145
145
|
## Optional: Plan Before Coding
|
|
146
146
|
|
|
@@ -295,9 +295,9 @@ builder provenance, start with
|
|
|
295
295
|
|
|
296
296
|
## Installation Status
|
|
297
297
|
|
|
298
|
-
The current public beta is `v0.5.0-beta.
|
|
298
|
+
The current public beta is `v0.5.0-beta.47` from
|
|
299
299
|
[codemower-ai/code-mower](https://github.com/codemower-ai/code-mower), published
|
|
300
|
-
as `code-mower==0.5.
|
|
300
|
+
as `code-mower==0.5.0b47` on [PyPI](https://pypi.org/project/code-mower/).
|
|
301
301
|
GitHub releases remain the auditable source for tags, build artifacts, and
|
|
302
302
|
release notes.
|
|
303
303
|
|
|
@@ -10,6 +10,7 @@ has proven the shared library on main.
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
import functools
|
|
13
|
+
import hashlib
|
|
13
14
|
import json
|
|
14
15
|
import os
|
|
15
16
|
import sys
|
|
@@ -24,7 +25,9 @@ import re
|
|
|
24
25
|
MIN_ABBREVIATED_SHA_LENGTH = 7
|
|
25
26
|
AUTHOR_EXCLUSION_ENV = "CODE_MOWER_AUTHOR_EXCLUSION_JSON"
|
|
26
27
|
ACTIONS_RUN_MARKER_RE = re.compile(
|
|
27
|
-
r"<!--\s*CODE_MOWER_AUDIT_RUN:\s*run_id=([0-9]+)
|
|
28
|
+
r"<!--\s*CODE_MOWER_AUDIT_RUN:\s*run_id=([0-9]+)"
|
|
29
|
+
r"(?:\s+comment_id=([0-9]+))?"
|
|
30
|
+
r"(?:\s+body_sha256=([0-9a-f]{64}))?\s*-->"
|
|
28
31
|
)
|
|
29
32
|
TRUSTED_ACTIONS_AUDIT_EVENTS = frozenset({"pull_request_target"})
|
|
30
33
|
|
|
@@ -44,6 +47,13 @@ class GitHubToken:
|
|
|
44
47
|
value: str
|
|
45
48
|
|
|
46
49
|
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class ActionsRunMarker:
|
|
52
|
+
run_id: str
|
|
53
|
+
comment_id: str
|
|
54
|
+
body_sha256: str
|
|
55
|
+
|
|
56
|
+
|
|
47
57
|
@dataclass(frozen=True)
|
|
48
58
|
class LaneConfig:
|
|
49
59
|
name: str
|
|
@@ -300,8 +310,32 @@ def parse_csv_set(raw: str) -> frozenset[str]:
|
|
|
300
310
|
|
|
301
311
|
|
|
302
312
|
def parse_audit_run_id(body: str) -> str:
|
|
313
|
+
marker = parse_audit_run_marker(body)
|
|
314
|
+
return marker.run_id if marker else ""
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def parse_audit_run_marker(body: str) -> Optional[ActionsRunMarker]:
|
|
318
|
+
match = ACTIONS_RUN_MARKER_RE.search(body)
|
|
319
|
+
if not match:
|
|
320
|
+
return None
|
|
321
|
+
return ActionsRunMarker(
|
|
322
|
+
run_id=match.group(1),
|
|
323
|
+
comment_id=match.group(2) or "",
|
|
324
|
+
body_sha256=match.group(3) or "",
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def audit_run_marker_body_digest_matches(body: str) -> bool:
|
|
303
329
|
match = ACTIONS_RUN_MARKER_RE.search(body)
|
|
304
|
-
|
|
330
|
+
if not match or not match.group(2) or not match.group(3):
|
|
331
|
+
return False
|
|
332
|
+
canonical_marker = (
|
|
333
|
+
"<!-- CODE_MOWER_AUDIT_RUN: "
|
|
334
|
+
f"run_id={match.group(1)} comment_id={match.group(2)} -->"
|
|
335
|
+
)
|
|
336
|
+
canonical_body = body[: match.start()] + canonical_marker + body[match.end() :]
|
|
337
|
+
digest = hashlib.sha256(canonical_body.encode("utf-8")).hexdigest()
|
|
338
|
+
return digest == match.group(3)
|
|
305
339
|
|
|
306
340
|
|
|
307
341
|
def workflow_path_matches(path: str, trusted_workflows: Sequence[str]) -> bool:
|
|
@@ -357,6 +391,7 @@ def github_actions_comment_attested(
|
|
|
357
391
|
*,
|
|
358
392
|
repo: str,
|
|
359
393
|
body: str,
|
|
394
|
+
comment_id: int | str | None = None,
|
|
360
395
|
issue_number: int,
|
|
361
396
|
head_sha: str,
|
|
362
397
|
workflow_paths: Sequence[str],
|
|
@@ -367,14 +402,19 @@ def github_actions_comment_attested(
|
|
|
367
402
|
"""Return whether a github-actions[bot] audit comment is tied to this PR head."""
|
|
368
403
|
trusted_workflows = tuple(parse_csv_set(",".join(workflow_paths)))
|
|
369
404
|
token_list = tuple(token for token in tokens if token.value)
|
|
370
|
-
|
|
371
|
-
|
|
405
|
+
marker = parse_audit_run_marker(body)
|
|
406
|
+
comment_id_text = str(comment_id or "").strip()
|
|
407
|
+
if not trusted_workflows or not token_list or marker is None or not head_sha:
|
|
408
|
+
return False
|
|
409
|
+
if not comment_id_text or marker.comment_id != comment_id_text:
|
|
410
|
+
return False
|
|
411
|
+
if not audit_run_marker_body_digest_matches(body):
|
|
372
412
|
return False
|
|
373
413
|
try:
|
|
374
414
|
if actions_run_lookup is None:
|
|
375
|
-
run = fetch_actions_run(repo, run_id, tokens=token_list)
|
|
415
|
+
run = fetch_actions_run(repo, marker.run_id, tokens=token_list)
|
|
376
416
|
else:
|
|
377
|
-
run = dict(actions_run_lookup(run_id))
|
|
417
|
+
run = dict(actions_run_lookup(marker.run_id))
|
|
378
418
|
except Exception:
|
|
379
419
|
return False
|
|
380
420
|
|
|
@@ -21,6 +21,7 @@ import tempfile
|
|
|
21
21
|
import time
|
|
22
22
|
import urllib.error
|
|
23
23
|
from dataclasses import dataclass, field, replace
|
|
24
|
+
from datetime import datetime, timezone
|
|
24
25
|
from pathlib import Path
|
|
25
26
|
from typing import Any, Dict, List, Optional, Tuple
|
|
26
27
|
|
|
@@ -33,7 +34,9 @@ if __package__ in {None, "", "tools"}:
|
|
|
33
34
|
from tools.claude_cli_environment import clean_claude_cli_env, env_flag
|
|
34
35
|
from tools.provider_runners import (
|
|
35
36
|
audit_runtime_quarantine_reason as _audit_runtime_quarantine_reason,
|
|
37
|
+
bind_actions_run_comment_id,
|
|
36
38
|
clip_text as _clip_text,
|
|
39
|
+
edit_pr_comment,
|
|
37
40
|
fetch_pull_request,
|
|
38
41
|
fetch_base_ref_sha as _shared_fetch_base_ref_sha,
|
|
39
42
|
fetch_pr_head_sha as _shared_fetch_pr_head_sha,
|
|
@@ -61,7 +64,9 @@ if __package__ in {None, "", "tools"}:
|
|
|
61
64
|
from claude_cli_environment import clean_claude_cli_env, env_flag # type: ignore
|
|
62
65
|
from provider_runners import ( # type: ignore
|
|
63
66
|
audit_runtime_quarantine_reason as _audit_runtime_quarantine_reason,
|
|
67
|
+
bind_actions_run_comment_id,
|
|
64
68
|
clip_text as _clip_text,
|
|
69
|
+
edit_pr_comment,
|
|
65
70
|
fetch_pull_request,
|
|
66
71
|
fetch_base_ref_sha as _shared_fetch_base_ref_sha,
|
|
67
72
|
fetch_pr_head_sha as _shared_fetch_pr_head_sha,
|
|
@@ -87,7 +92,9 @@ else: # pragma: no cover - exercised after package extraction.
|
|
|
87
92
|
from .provider_runners import (
|
|
88
93
|
FetchedHeadMismatch,
|
|
89
94
|
audit_runtime_quarantine_reason as _audit_runtime_quarantine_reason,
|
|
95
|
+
bind_actions_run_comment_id,
|
|
90
96
|
clip_text as _clip_text,
|
|
97
|
+
edit_pr_comment,
|
|
91
98
|
fetch_base_ref_sha as _shared_fetch_base_ref_sha,
|
|
92
99
|
fetch_pull_request,
|
|
93
100
|
fetch_pr_head_sha as _shared_fetch_pr_head_sha,
|
|
@@ -123,11 +130,32 @@ MIN_BLOCKED_VERDICT_PROSE_CHARS = 80
|
|
|
123
130
|
SCHEMA_PLACEHOLDER_TEXT = frozenset({"test", "example", "placeholder"})
|
|
124
131
|
CLAUDE_RAW_OUTPUT_SCHEMA = "code_mower.claudeAuditRawOutput.v1"
|
|
125
132
|
BLOCKER_SEVERITIES = frozenset({"P0", "P1", "P2"})
|
|
133
|
+
DEFAULT_CLAUDE_CLI_FAILURE_DIR = (
|
|
134
|
+
Path.home() / ".cache" / "code-mower-audits" / "cli-failures"
|
|
135
|
+
)
|
|
126
136
|
|
|
127
137
|
STALE_TRAILER = "<!-- CLAUDE_AUDIT_STATE: needs-claude-audit -->"
|
|
128
138
|
DONE_TRAILER = "<!-- CLAUDE_AUDIT_STATE: claude-audit-done -->"
|
|
129
139
|
BLOCKED_TRAILER = "<!-- CLAUDE_AUDIT_STATE: claude-audit-blocked -->"
|
|
130
140
|
AUDIT_RUN_TRAILER_PREFIX = "<!-- CODE_MOWER_AUDIT_RUN:"
|
|
141
|
+
STALE_REQUEUE_MARKER = "<!-- CODE_MOWER_AUDIT_REQUEUE: kind=stale -->"
|
|
142
|
+
UNKNOWN_REQUEUE_MARKER = "<!-- CODE_MOWER_AUDIT_REQUEUE: kind=unknown -->"
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _post_audit_comment(
|
|
146
|
+
repo: str,
|
|
147
|
+
pr_number: int,
|
|
148
|
+
body: str,
|
|
149
|
+
*,
|
|
150
|
+
token: str,
|
|
151
|
+
actions_run_id: Optional[str],
|
|
152
|
+
) -> tuple[dict[str, Any], str]:
|
|
153
|
+
posted = post_pr_comment(repo, pr_number, body, token=token)
|
|
154
|
+
if not actions_run_id:
|
|
155
|
+
return posted, body
|
|
156
|
+
bound_body = bind_actions_run_comment_id(body, posted.get("id"))
|
|
157
|
+
edited = edit_pr_comment(repo, posted["id"], bound_body, token=token)
|
|
158
|
+
return edited if isinstance(edited, dict) else posted, bound_body
|
|
131
159
|
|
|
132
160
|
|
|
133
161
|
CLAUDE_VERDICT_SCHEMA: Dict[str, Any] = {
|
|
@@ -492,6 +520,104 @@ def _write_claude_raw_output_sidecar(
|
|
|
492
520
|
)
|
|
493
521
|
|
|
494
522
|
|
|
523
|
+
def _claude_status_payload(stdout: str, stderr: str) -> Dict[str, Any]:
|
|
524
|
+
candidates = [stdout.strip(), stderr.strip()]
|
|
525
|
+
candidates.extend(
|
|
526
|
+
line.strip()
|
|
527
|
+
for text in (stdout, stderr)
|
|
528
|
+
for line in reversed(text.splitlines())
|
|
529
|
+
)
|
|
530
|
+
for candidate in candidates:
|
|
531
|
+
if not candidate:
|
|
532
|
+
continue
|
|
533
|
+
try:
|
|
534
|
+
payload = json.loads(candidate)
|
|
535
|
+
except json.JSONDecodeError:
|
|
536
|
+
continue
|
|
537
|
+
if isinstance(payload, dict):
|
|
538
|
+
return payload
|
|
539
|
+
return {}
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def _claude_payload_text(payload: Dict[str, Any], key: str, limit: int = 500) -> str:
|
|
543
|
+
value = payload.get(key)
|
|
544
|
+
if value is None:
|
|
545
|
+
return ""
|
|
546
|
+
if isinstance(value, (dict, list)):
|
|
547
|
+
text = json.dumps(value, sort_keys=True)
|
|
548
|
+
else:
|
|
549
|
+
text = str(value)
|
|
550
|
+
return _one_line(text, limit)
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def _claude_cli_failure_reason(stdout: str, stderr: str) -> str:
|
|
554
|
+
payload = _claude_status_payload(stdout, stderr)
|
|
555
|
+
if not payload:
|
|
556
|
+
return ""
|
|
557
|
+
result = _claude_payload_text(payload, "result", 160)
|
|
558
|
+
terminal_reason = _claude_payload_text(payload, "terminal_reason", 160)
|
|
559
|
+
status = _claude_payload_text(payload, "api_error_status", 40)
|
|
560
|
+
reason = result.split(" · ", 1)[0].strip() or terminal_reason
|
|
561
|
+
if not reason and status:
|
|
562
|
+
reason = f"API error {status}"
|
|
563
|
+
return f"Claude CLI: {reason}" if reason else ""
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def _dump_claude_cli_failure(
|
|
567
|
+
*,
|
|
568
|
+
repo: str,
|
|
569
|
+
pr_number: int,
|
|
570
|
+
head_sha: str,
|
|
571
|
+
stdout: str,
|
|
572
|
+
stderr: str,
|
|
573
|
+
reason: str,
|
|
574
|
+
base_dir: Optional[Path] = None,
|
|
575
|
+
) -> Optional[Path]:
|
|
576
|
+
try:
|
|
577
|
+
base_dir = base_dir or DEFAULT_CLAUDE_CLI_FAILURE_DIR
|
|
578
|
+
base_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
579
|
+
try:
|
|
580
|
+
os.chmod(base_dir, 0o700)
|
|
581
|
+
except OSError:
|
|
582
|
+
pass
|
|
583
|
+
owner_name = repo.replace("/", "_")
|
|
584
|
+
short_sha = (head_sha or "unknown")[:12]
|
|
585
|
+
ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S_%fZ")
|
|
586
|
+
path = base_dir / f"{owner_name}_pr{pr_number}_{short_sha}_{ts}_claude.log"
|
|
587
|
+
payload = _claude_status_payload(stdout, stderr)
|
|
588
|
+
field_lines = ""
|
|
589
|
+
for key in ("result", "terminal_reason", "api_error_status"):
|
|
590
|
+
value = _claude_payload_text(payload, key)
|
|
591
|
+
if value:
|
|
592
|
+
field_lines += f"# claude_{key}: {value}\n"
|
|
593
|
+
body = (
|
|
594
|
+
"# Claude CLI raw output - structured verdict was unusable\n"
|
|
595
|
+
f"# repo: {repo}\n"
|
|
596
|
+
f"# pr: {pr_number}\n"
|
|
597
|
+
f"# head_sha: {head_sha}\n"
|
|
598
|
+
f"# captured_at: {ts}\n"
|
|
599
|
+
f"# diagnostic_reason: {reason}\n"
|
|
600
|
+
f"{field_lines}"
|
|
601
|
+
f"# stdout_bytes: {len(stdout.encode('utf-8'))}\n"
|
|
602
|
+
f"# stderr_bytes: {len(stderr.encode('utf-8'))}\n"
|
|
603
|
+
"\n===== STDOUT =====\n"
|
|
604
|
+
+ (stdout if stdout else "<empty>\n")
|
|
605
|
+
+ "\n===== STDERR =====\n"
|
|
606
|
+
+ (stderr if stderr else "<empty>\n")
|
|
607
|
+
)
|
|
608
|
+
fd = os.open(
|
|
609
|
+
str(path),
|
|
610
|
+
os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0),
|
|
611
|
+
0o600,
|
|
612
|
+
)
|
|
613
|
+
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
|
614
|
+
handle.write(body)
|
|
615
|
+
return path
|
|
616
|
+
except OSError as exc:
|
|
617
|
+
print(f" warning: failed to dump Claude CLI output: {exc}", file=sys.stderr)
|
|
618
|
+
return None
|
|
619
|
+
|
|
620
|
+
|
|
495
621
|
def _guardrail_retry_prompt(
|
|
496
622
|
prompt: str,
|
|
497
623
|
reason: str,
|
|
@@ -899,6 +1025,7 @@ def format_comment(
|
|
|
899
1025
|
is_unknown: bool = False,
|
|
900
1026
|
merge_authority: bool = True,
|
|
901
1027
|
actions_run_id: Optional[str] = None,
|
|
1028
|
+
unknown_reason: str = "",
|
|
902
1029
|
) -> str:
|
|
903
1030
|
posture = "merge-authority lane" if merge_authority else "informational only"
|
|
904
1031
|
header = f"## Claude audit ({posture})\n\n"
|
|
@@ -911,16 +1038,23 @@ def format_comment(
|
|
|
911
1038
|
+ f"\nHead SHA changed during review (`{head_sha[:8]}` -> "
|
|
912
1039
|
+ f"`{(stale_end_sha or '?')[:8]}`). Skipping this verdict and "
|
|
913
1040
|
+ "requeuing for re-review of the new head.\n\n"
|
|
1041
|
+
+ STALE_REQUEUE_MARKER
|
|
1042
|
+
+ "\n"
|
|
914
1043
|
+ STALE_TRAILER
|
|
915
1044
|
+ "\n"
|
|
916
1045
|
)
|
|
917
1046
|
return limit_comment_body(body, STALE_TRAILER, provider_name="Claude")
|
|
918
1047
|
if is_unknown:
|
|
1048
|
+
reason_line = f"\n\n{unknown_reason}." if unknown_reason else ""
|
|
919
1049
|
body = (
|
|
920
1050
|
header
|
|
921
1051
|
+ "\nCould not validate a Claude structured verdict artifact. "
|
|
922
1052
|
+ "The CLI may have failed, exceeded budget, or emitted no "
|
|
923
|
-
+ "schema-valid structured output. Requeuing for re-review
|
|
1053
|
+
+ "schema-valid structured output. Requeuing for re-review."
|
|
1054
|
+
+ reason_line
|
|
1055
|
+
+ "\n\n"
|
|
1056
|
+
+ UNKNOWN_REQUEUE_MARKER
|
|
1057
|
+
+ "\n"
|
|
924
1058
|
+ STALE_TRAILER
|
|
925
1059
|
+ "\n"
|
|
926
1060
|
)
|
|
@@ -996,6 +1130,7 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
|
|
|
996
1130
|
config.max_diff_hard_limit_bytes,
|
|
997
1131
|
)
|
|
998
1132
|
except FetchedHeadMismatch as exc:
|
|
1133
|
+
actions_run_id = os.environ.get("GITHUB_RUN_ID") or None
|
|
999
1134
|
print(
|
|
1000
1135
|
f" force-push race: fetched head {exc.actual_sha[:8]} does not "
|
|
1001
1136
|
f"match recorded head {exc.expected_sha[:8]}; emitting STALE",
|
|
@@ -1012,6 +1147,7 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
|
|
|
1012
1147
|
is_stale=True,
|
|
1013
1148
|
stale_end_sha=exc.actual_sha,
|
|
1014
1149
|
merge_authority=config.merge_authority,
|
|
1150
|
+
actions_run_id=actions_run_id,
|
|
1015
1151
|
)
|
|
1016
1152
|
result = ClaudeAuditResult(
|
|
1017
1153
|
repo=repo,
|
|
@@ -1066,7 +1202,14 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
|
|
|
1066
1202
|
quarantine_reason,
|
|
1067
1203
|
)
|
|
1068
1204
|
result.comment_body = comment_body
|
|
1069
|
-
posted
|
|
1205
|
+
posted, comment_body = _post_audit_comment(
|
|
1206
|
+
repo,
|
|
1207
|
+
pr_number,
|
|
1208
|
+
comment_body,
|
|
1209
|
+
token=config.github_token,
|
|
1210
|
+
actions_run_id=actions_run_id,
|
|
1211
|
+
)
|
|
1212
|
+
result.comment_body = comment_body
|
|
1070
1213
|
result.posted_comment_url = posted.get("html_url")
|
|
1071
1214
|
_record_posted_comment_url(
|
|
1072
1215
|
result.verdict_artifact_path,
|
|
@@ -1181,6 +1324,7 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
|
|
|
1181
1324
|
head_sha_end = pr_meta_after["head"]["sha"]
|
|
1182
1325
|
is_stale = head_sha_start != head_sha_end
|
|
1183
1326
|
actions_run_id = os.environ.get("GITHUB_RUN_ID") or None
|
|
1327
|
+
claude_cli_reason = _claude_cli_failure_reason(claude_stdout, claude_stderr)
|
|
1184
1328
|
|
|
1185
1329
|
if is_stale:
|
|
1186
1330
|
comment_body = format_comment(
|
|
@@ -1200,6 +1344,7 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
|
|
|
1200
1344
|
is_unknown=True,
|
|
1201
1345
|
merge_authority=config.merge_authority,
|
|
1202
1346
|
actions_run_id=actions_run_id,
|
|
1347
|
+
unknown_reason=claude_cli_reason,
|
|
1203
1348
|
)
|
|
1204
1349
|
result_verdict = "UNKNOWN"
|
|
1205
1350
|
trailer = STALE_TRAILER
|
|
@@ -1227,6 +1372,21 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
|
|
|
1227
1372
|
)
|
|
1228
1373
|
|
|
1229
1374
|
if not config.dry_run:
|
|
1375
|
+
if result_verdict == "UNKNOWN":
|
|
1376
|
+
dump_path = _dump_claude_cli_failure(
|
|
1377
|
+
repo=repo,
|
|
1378
|
+
pr_number=pr_number,
|
|
1379
|
+
head_sha=head_sha_start,
|
|
1380
|
+
stdout=claude_stdout,
|
|
1381
|
+
stderr=claude_stderr,
|
|
1382
|
+
reason=parsed.prose,
|
|
1383
|
+
)
|
|
1384
|
+
if dump_path is not None:
|
|
1385
|
+
print(
|
|
1386
|
+
f" captured Claude CLI output for diagnosis: {dump_path}",
|
|
1387
|
+
file=sys.stderr,
|
|
1388
|
+
flush=True,
|
|
1389
|
+
)
|
|
1230
1390
|
quarantine_reason = _audit_runtime_quarantine_reason(
|
|
1231
1391
|
comment_body=comment_body,
|
|
1232
1392
|
fixture_reason=final_fixture_reason if parsed.verdict == "UNKNOWN" else None,
|
|
@@ -1269,7 +1429,14 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
|
|
|
1269
1429
|
quarantine_reason,
|
|
1270
1430
|
)
|
|
1271
1431
|
result.comment_body = comment_body
|
|
1272
|
-
posted
|
|
1432
|
+
posted, comment_body = _post_audit_comment(
|
|
1433
|
+
repo,
|
|
1434
|
+
pr_number,
|
|
1435
|
+
comment_body,
|
|
1436
|
+
token=config.github_token,
|
|
1437
|
+
actions_run_id=actions_run_id,
|
|
1438
|
+
)
|
|
1439
|
+
result.comment_body = comment_body
|
|
1273
1440
|
result.posted_comment_url = posted.get("html_url")
|
|
1274
1441
|
_record_posted_comment_url(
|
|
1275
1442
|
result.verdict_artifact_path,
|
|
@@ -1534,7 +1701,11 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
1534
1701
|
print(f"spend metadata appended to {args.spend_path}", file=sys.stderr)
|
|
1535
1702
|
except (OSError, ValueError) as exc:
|
|
1536
1703
|
print(f"warning: failed to append spend metadata: {exc}", file=sys.stderr)
|
|
1537
|
-
return
|
|
1704
|
+
return _audit_exit_code(result.verdict)
|
|
1705
|
+
|
|
1706
|
+
|
|
1707
|
+
def _audit_exit_code(verdict: str) -> int:
|
|
1708
|
+
return 2 if verdict == "UNKNOWN" else 0
|
|
1538
1709
|
|
|
1539
1710
|
|
|
1540
1711
|
if __name__ == "__main__":
|
|
@@ -14,7 +14,7 @@ from pathlib import Path
|
|
|
14
14
|
if __package__ in {None, ""}:
|
|
15
15
|
raise SystemExit(
|
|
16
16
|
"code_mower.cli is a packaged entrypoint. Install Code Mower with "
|
|
17
|
-
"`pipx install code-mower==0.5.
|
|
17
|
+
"`pipx install code-mower==0.5.0b47`, or run source checkouts with "
|
|
18
18
|
"`PYTHONPATH=src python -m code_mower.cli`."
|
|
19
19
|
)
|
|
20
20
|
|
|
@@ -229,6 +229,8 @@ def reviewer_run_event_from_verdict_artifact(
|
|
|
229
229
|
duration_seconds = _artifact_duration_seconds(payload)
|
|
230
230
|
if duration_seconds is not None:
|
|
231
231
|
metrics["duration_seconds_total"] = duration_seconds
|
|
232
|
+
metrics["duration_seconds"] = duration_seconds
|
|
233
|
+
metrics["wall_seconds"] = duration_seconds
|
|
232
234
|
dimensions["duration_source"] = "verdict_artifact"
|
|
233
235
|
return {
|
|
234
236
|
"schema": BENCHMARK_EVENT_SCHEMA,
|
|
@@ -57,7 +57,7 @@ daemon analogous to local_llm_audit_bridge.py):
|
|
|
57
57
|
Exit codes (CLI mode):
|
|
58
58
|
0 comment posted (or dry-run printed)
|
|
59
59
|
1 generic error (config, network, Codex CLI failure)
|
|
60
|
-
2
|
|
60
|
+
2 UNKNOWN verdict emitted (caller should requeue and investigate)
|
|
61
61
|
|
|
62
62
|
Trailer protocol (mirrors Devin/local LLM):
|
|
63
63
|
<!-- CODEX_AUDIT_STATE: codex-audit-done --> (verdict was PASS)
|
|
@@ -90,7 +90,9 @@ if __package__ in {None, "", "tools"}:
|
|
|
90
90
|
from tools.audit_progress import AuditProgress, run_subprocess_with_progress
|
|
91
91
|
from tools.provider_runners import (
|
|
92
92
|
audit_runtime_quarantine_reason as _audit_runtime_quarantine_reason,
|
|
93
|
+
bind_actions_run_comment_id,
|
|
93
94
|
clip_text as _clip_text,
|
|
95
|
+
edit_pr_comment,
|
|
94
96
|
fetch_pull_request,
|
|
95
97
|
fetch_base_ref as _shared_fetch_base_ref,
|
|
96
98
|
fetch_pr_head as _shared_fetch_pr_head,
|
|
@@ -115,7 +117,9 @@ if __package__ in {None, "", "tools"}:
|
|
|
115
117
|
from audit_progress import AuditProgress, run_subprocess_with_progress # type: ignore
|
|
116
118
|
from provider_runners import ( # type: ignore
|
|
117
119
|
audit_runtime_quarantine_reason as _audit_runtime_quarantine_reason,
|
|
120
|
+
bind_actions_run_comment_id,
|
|
118
121
|
clip_text as _clip_text,
|
|
122
|
+
edit_pr_comment,
|
|
119
123
|
fetch_pull_request,
|
|
120
124
|
fetch_base_ref as _shared_fetch_base_ref,
|
|
121
125
|
fetch_pr_head as _shared_fetch_pr_head,
|
|
@@ -140,8 +144,10 @@ else: # pragma: no cover - exercised after package extraction.
|
|
|
140
144
|
from .audit_progress import AuditProgress, run_subprocess_with_progress
|
|
141
145
|
from .provider_runners import (
|
|
142
146
|
audit_runtime_quarantine_reason as _audit_runtime_quarantine_reason,
|
|
147
|
+
bind_actions_run_comment_id,
|
|
143
148
|
clip_text as _clip_text,
|
|
144
149
|
create_temp_worktree as _shared_create_temp_worktree,
|
|
150
|
+
edit_pr_comment,
|
|
145
151
|
fetch_base_ref as _shared_fetch_base_ref,
|
|
146
152
|
fetch_pull_request,
|
|
147
153
|
fetch_pr_head as _shared_fetch_pr_head,
|
|
@@ -319,6 +325,24 @@ STALE_TRAILER = "<!-- CODEX_AUDIT_STATE: needs-codex-audit -->"
|
|
|
319
325
|
DONE_TRAILER = "<!-- CODEX_AUDIT_STATE: codex-audit-done -->"
|
|
320
326
|
BLOCKED_TRAILER = "<!-- CODEX_AUDIT_STATE: codex-audit-blocked -->"
|
|
321
327
|
AUDIT_RUN_TRAILER_PREFIX = "<!-- CODE_MOWER_AUDIT_RUN:"
|
|
328
|
+
STALE_REQUEUE_MARKER = "<!-- CODE_MOWER_AUDIT_REQUEUE: kind=stale -->"
|
|
329
|
+
UNKNOWN_REQUEUE_MARKER = "<!-- CODE_MOWER_AUDIT_REQUEUE: kind=unknown -->"
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _post_audit_comment(
|
|
333
|
+
repo: str,
|
|
334
|
+
pr_number: int,
|
|
335
|
+
body: str,
|
|
336
|
+
*,
|
|
337
|
+
token: str,
|
|
338
|
+
actions_run_id: Optional[str],
|
|
339
|
+
) -> tuple[dict[str, Any], str]:
|
|
340
|
+
posted = post_pr_comment(repo, pr_number, body, token=token)
|
|
341
|
+
if not actions_run_id:
|
|
342
|
+
return posted, body
|
|
343
|
+
bound_body = bind_actions_run_comment_id(body, posted.get("id"))
|
|
344
|
+
edited = edit_pr_comment(repo, posted["id"], bound_body, token=token)
|
|
345
|
+
return edited if isinstance(edited, dict) else posted, bound_body
|
|
322
346
|
|
|
323
347
|
|
|
324
348
|
def _quarantine_is_test_only(reason: Optional[str]) -> bool:
|
|
@@ -1242,6 +1266,8 @@ def format_comment(
|
|
|
1242
1266
|
+ f"\nHead SHA changed during review (`{head_sha[:8]}` → "
|
|
1243
1267
|
+ f"`{(stale_end_sha or '?')[:8]}`). Skipping this verdict and "
|
|
1244
1268
|
+ "requeuing for re-review of the new head.\n\n"
|
|
1269
|
+
+ STALE_REQUEUE_MARKER
|
|
1270
|
+
+ "\n"
|
|
1245
1271
|
+ STALE_TRAILER
|
|
1246
1272
|
+ "\n"
|
|
1247
1273
|
)
|
|
@@ -1254,6 +1280,8 @@ def format_comment(
|
|
|
1254
1280
|
+ "the structured-output pass may have failed, or the verdict "
|
|
1255
1281
|
+ "format may have drifted. "
|
|
1256
1282
|
+ "Requeuing for re-review.\n\n"
|
|
1283
|
+
+ UNKNOWN_REQUEUE_MARKER
|
|
1284
|
+
+ "\n"
|
|
1257
1285
|
+ STALE_TRAILER
|
|
1258
1286
|
+ "\n"
|
|
1259
1287
|
)
|
|
@@ -1389,6 +1417,7 @@ def audit_pr(config: AuditConfig, repo: str, pr_number: int) -> AuditResult:
|
|
|
1389
1417
|
pr_meta_after = fetch_pull_request(repo, pr_number, token=config.github_token)
|
|
1390
1418
|
head_sha_after = pr_meta_after["head"]["sha"]
|
|
1391
1419
|
if head_sha_after != head_sha_start:
|
|
1420
|
+
actions_run_id = os.environ.get("GITHUB_RUN_ID") or None
|
|
1392
1421
|
print(f" force-push race: head moved from {head_sha_start[:8]} "
|
|
1393
1422
|
f"to {head_sha_after[:8]} during fetch; emitting STALE",
|
|
1394
1423
|
file=sys.stderr, flush=True)
|
|
@@ -1399,6 +1428,7 @@ def audit_pr(config: AuditConfig, repo: str, pr_number: int) -> AuditResult:
|
|
|
1399
1428
|
is_stale=True,
|
|
1400
1429
|
stale_end_sha=head_sha_after,
|
|
1401
1430
|
merge_authority=config.merge_authority,
|
|
1431
|
+
actions_run_id=actions_run_id,
|
|
1402
1432
|
)
|
|
1403
1433
|
result = AuditResult(
|
|
1404
1434
|
repo=repo, pr_number=pr_number,
|
|
@@ -1458,8 +1488,14 @@ def audit_pr(config: AuditConfig, repo: str, pr_number: int) -> AuditResult:
|
|
|
1458
1488
|
quarantine_reason,
|
|
1459
1489
|
)
|
|
1460
1490
|
result.comment_body = comment_body
|
|
1461
|
-
posted =
|
|
1462
|
-
|
|
1491
|
+
posted, comment_body = _post_audit_comment(
|
|
1492
|
+
repo,
|
|
1493
|
+
pr_number,
|
|
1494
|
+
comment_body,
|
|
1495
|
+
token=config.github_token,
|
|
1496
|
+
actions_run_id=actions_run_id,
|
|
1497
|
+
)
|
|
1498
|
+
result.comment_body = comment_body
|
|
1463
1499
|
result.posted_comment_url = posted.get("html_url")
|
|
1464
1500
|
config.progress.emit(
|
|
1465
1501
|
"audit",
|
|
@@ -1657,8 +1693,14 @@ def audit_pr(config: AuditConfig, repo: str, pr_number: int) -> AuditResult:
|
|
|
1657
1693
|
quarantine_reason,
|
|
1658
1694
|
)
|
|
1659
1695
|
result.comment_body = comment_body
|
|
1660
|
-
posted =
|
|
1661
|
-
|
|
1696
|
+
posted, comment_body = _post_audit_comment(
|
|
1697
|
+
repo,
|
|
1698
|
+
pr_number,
|
|
1699
|
+
comment_body,
|
|
1700
|
+
token=config.github_token,
|
|
1701
|
+
actions_run_id=actions_run_id,
|
|
1702
|
+
)
|
|
1703
|
+
result.comment_body = comment_body
|
|
1662
1704
|
result.posted_comment_url = posted.get("html_url")
|
|
1663
1705
|
print(f"posted {repo}#{pr_number} verdict={result_verdict} "
|
|
1664
1706
|
f"url={result.posted_comment_url}", file=sys.stderr)
|
|
@@ -2016,14 +2058,15 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
2016
2058
|
except (OSError, ValueError) as exc:
|
|
2017
2059
|
print(f"warning: failed to append spend metadata: {exc}", file=sys.stderr)
|
|
2018
2060
|
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
#
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2061
|
+
return _audit_exit_code(result.verdict)
|
|
2062
|
+
|
|
2063
|
+
|
|
2064
|
+
def _audit_exit_code(verdict: str) -> int:
|
|
2065
|
+
# UNKNOWN still means the lane produced no trustworthy verdict and should
|
|
2066
|
+
# fail loudly. STALE means a newer head superseded this run after the wrapper
|
|
2067
|
+
# posted a requeue note, so returning success avoids alarm-grade noise while
|
|
2068
|
+
# the newer head's audit becomes authoritative.
|
|
2069
|
+
return 2 if verdict == "UNKNOWN" else 0
|
|
2027
2070
|
|
|
2028
2071
|
|
|
2029
2072
|
if __name__ == "__main__":
|