code-mower 0.5.0b41__tar.gz → 0.5.0b43__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.0b41 → code_mower-0.5.0b43}/PKG-INFO +6 -6
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/README.md +5 -5
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/pyproject.toml +1 -1
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/__init__.py +1 -1
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/audit_labeler_lib.py +129 -1
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/claude_audit_pr.py +222 -4
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/cli.py +1 -1
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/cloud.py +7 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/cloud_client/operations.py +8 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/code_mower_telemetry.py +5 -1
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/codex_audit_pr.py +28 -4
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/init.py +91 -1
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/lane_configs/claude.py +1 -1
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/lane_configs/codex.py +1 -1
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/package_manifest.py +2 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/provider_runners/__init__.py +6 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/provider_runners/pr_worktree.py +36 -1
- {code_mower-0.5.0b41 → code_mower-0.5.0b43/src/code_mower}/templates/workflows/builder-provenance.yml.j2 +1 -1
- code_mower-0.5.0b43/src/code_mower/templates/workflows/code-mower-gate-health.yml.j2 +321 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43/src/code_mower}/templates/workflows/code-mower-gate.yml.j2 +36 -4
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/self-hosted-local-audit.yml.j2 +77 -5
- {code_mower-0.5.0b41 → code_mower-0.5.0b43/src/code_mower}/templates/workflows/trailer-comment-labeler.yml.j2 +2 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/trailer_comment_labeler.py +39 -3
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower.egg-info/PKG-INFO +6 -6
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower.egg-info/SOURCES.txt +3 -0
- code_mower-0.5.0b43/templates/workflows/code-mower-gate-health.yml.j2 +321 -0
- {code_mower-0.5.0b41/src/code_mower → code_mower-0.5.0b43}/templates/workflows/code-mower-gate.yml.j2 +36 -4
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/workflows/self-hosted-local-audit.yml.j2 +77 -5
- {code_mower-0.5.0b41/src/code_mower → code_mower-0.5.0b43}/templates/workflows/trailer-comment-labeler.yml.j2 +2 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_audit_comment_posture.py +15 -0
- code_mower-0.5.0b43/tests/test_claude_audit_pr.py +175 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_provider_runners_pr_worktree.py +58 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_release_hygiene.py +461 -38
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_reviewer_metrics_core.py +47 -0
- code_mower-0.5.0b43/tests/test_trailer_comment_labeler.py +151 -0
- code_mower-0.5.0b41/tests/test_trailer_comment_labeler.py +0 -66
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/CODE_OF_CONDUCT.md +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/LICENSE +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/MANIFEST.in +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/NOTICE +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/SUPPORT.md +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/requirements/requirements.txt +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/setup.cfg +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/adapters/__init__.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/adapters/_base.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/adapters/cursor_bugbot.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/adapters/gitar.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/adapters/greptile.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/adapters/qodo.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/antigravity_cli_audit_pr.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/audit_handoff_log.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/audit_progress.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/blind_review_artifacts.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/blind_review_coordinator.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/bootstrap.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/builder_experiment.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/builder_runs.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/__init__.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/arms.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/auto_discovery.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/commands.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/context_inputs.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/corpus.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/effect_report.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/evidence.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/evidence_report.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/identity.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/metrics.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/overlap.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/planning.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/policy.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/results.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/run_results.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/run_status.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/runner.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/truth.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/calibration/value_report.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/checks.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/claude_cli_bounce.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/claude_cli_environment.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/clear_stale.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/cloud_client/__init__.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/cloud_client/bundle.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/cloud_client/doctor.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/cloud_client/dogfood.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/cloud_client/endpoints.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/cloud_client/errors.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/cloud_client/events.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/cloud_client/export.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/cloud_client/git_metadata.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/cloud_client/manifest.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/cloud_client/reports.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/cloud_client/setup.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/cloud_client/upload.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/code_mower_calibration.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/code_mower_context_packs.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/code_mower_merge.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/coderabbit_cli_audit_pr.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/codex_audit_env_preflight.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/codex_audit_schema_smoke.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/codex_audit_verdict.schema.json +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/config.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/__init__.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/cloud.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/common.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_actions.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_actions_cost.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_actions_cost_summary.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_actions_failure_annotations.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_actions_failure_models.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_actions_failure_scan.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_actions_failure_selection.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_actions_failures.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_actions_permissions.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_api.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_branch.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_config.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_provider.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_repo.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/groups.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/models.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/output.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/presets.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/privacy.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_api_model.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_api_model_openai.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_api_model_profiles.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_env.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_env_required.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_env_tokens.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_local_cli.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_local_cli_commands.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_local_cli_probe_config.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_probe.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_probe_auth.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_probe_evaluation.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_probe_json.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_probe_remediation.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_review_hygiene.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/providers.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/registry.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/runner.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/runtime.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/runtime_github_auth.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/gemini_cli_audit_pr.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/grok_build_audit_pr.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/hermes_cli_audit_pr.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/lane_configs/__init__.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/lane_configs/aider.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/lane_configs/antigravity_cli.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/lane_configs/devin.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/lane_configs/gemini_cli.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/lane_configs/grok_build.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/lane_configs/hermes_cli.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/lane_configs/local_llm.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/local_llm_audit_pr.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/local_llm_bakeoff.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/local_llm_calibration.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/local_llm_profiles.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/migration.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/migration_install.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/migration_mirror.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/migration_readiness.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/migration_rehearsal.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/next_steps.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/package.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/package_content.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/package_paths.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/package_rendering.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/package_static.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/plan_context.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/prompts.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/provider_registry.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/provider_runners/comments.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/provider_runners/git.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/provider_runners/github_auth.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/provider_runners/github_pr.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/provider_runners/process.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/provider_runners/repo_paths.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/provider_runners/text_schema.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/provider_runners/verdict_artifacts.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/provider_runners/workspace.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/providers/__init__.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/providers/local_cli.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/providers/provenance.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/release_readiness.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/reviewer_metrics.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/reviewer_spend.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/saas_reviewer_labeler.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/secrets.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/builder-experiment.example.json +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/calibration-corpus.example.json +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/calibration-corpus.json +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/code-mower.example.yml +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/context-packs.example.json +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/lane_prompts/base-audit.md +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/lane_prompts/calibration-policy.md +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/lane_prompts/context-driven-quality.md +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/lane_prompts/docs-design.md +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/lane_prompts/generic-programming.md +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/lane_prompts/operability.md +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/lane_prompts/package-runtime.md +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/lane_prompts/plan-conformance.md +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/lane_prompts/security-threat-model.md +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/product-support/code_mower +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/product-support/code_mower_standalone_pin.env +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/product-support/code_mower_standalone_shadow.sh +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/product-support/run_claude_audit_pr.sh +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/product-support/run_codex_audit_pr.sh +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/product-support/safe_gh_comment.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/product-support/status_report.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/providers.yml +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/reviewer-spend.example.json +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/reviewer-value-report.example.md +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/audit-label-cleanup.yml.j2 +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/hosted-bridge.yml.j2 +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/local-cli-audit.yml.j2 +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/needs-owner-notify.yml.j2 +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/private-standalone-shadow.yml.j2 +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/review-clear-stale.yml.j2 +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/saas-reviewer-labeler.yml.j2 +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/weekly-status.yml.j2 +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/versioning.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower/work_orders.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower.egg-info/dependency_links.txt +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower.egg-info/entry_points.txt +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower.egg-info/requires.txt +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/src/code_mower.egg-info/top_level.txt +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/builder-experiment.example.json +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/calibration-corpus.example.json +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/calibration-corpus.json +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/code-mower.yml.j2 +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/context-packs.example.json +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/cursor/BUGBOT.md +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/providers/acp_bridge.yml +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/providers/aider.yml +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/providers/antigravity_cli.yml +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/providers/claude_audit.yml +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/providers/claude_review.yml +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/providers/coderabbit_cli.yml +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/providers/codex.yml +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/providers/cursor_bugbot.yml +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/providers/devin.yml +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/providers/gemini_cli.yml +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/providers/gitar.yml +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/providers/greptile.yml +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/providers/hermes_cli.yml +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/providers/local_llm.yml +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/providers/qodo.yml +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/providers.yml +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/reviewer-spend.example.json +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/reviewer-value-report.example.md +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/workflows/audit-label-cleanup.yml.j2 +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/workflows/blind-review-artifacts-dry-run.yml.j2 +0 -0
- {code_mower-0.5.0b41/src/code_mower → code_mower-0.5.0b43}/templates/workflows/builder-provenance.yml.j2 +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/workflows/hosted-bridge.yml.j2 +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/workflows/local-cli-audit.yml.j2 +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/workflows/private-standalone-shadow.yml.j2 +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/workflows/review-clear-stale.yml.j2 +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/templates/workflows/saas-reviewer-labeler.yml.j2 +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_builder_identity.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_builder_runs.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_calibration_effect_report.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_calibration_truth.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_checks.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_claude_cli_bounce.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_clear_stale.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_cloud_client.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_cloud_git_metadata.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_cloud_manifest.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_cloud_reports.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_coderabbit_cli_audit_pr.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_codex_audit_env_preflight.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_doctor_actions_cost_summary.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_doctor_actions_failure_selection.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_doctor_boundaries.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_doctor_github_checks.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_doctor_output.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_doctor_privacy.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_doctor_provider_env_required.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_doctor_provider_env_tokens.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_doctor_provider_probe.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_doctor_provider_review_hygiene.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_doctor_registry.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_local_llm_github_helpers.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_plan_context.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_provider_local_cli.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_provider_runners_comments.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_provider_runners_git.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_provider_runners_github_pr.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_provider_runners_process.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_provider_runners_repo_paths.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_provider_runners_text_schema.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_provider_runners_workspace.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/tests/test_reviewer_spend.py +0 -0
- {code_mower-0.5.0b41 → code_mower-0.5.0b43}/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.0b43
|
|
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.0b43
|
|
107
107
|
code-mower --version
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
`0.5.
|
|
110
|
+
`0.5.0b43` 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.0b43`.
|
|
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.43` 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.0b43` 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.0b43
|
|
92
92
|
code-mower --version
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
`0.5.
|
|
95
|
+
`0.5.0b43` 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.0b43`.
|
|
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.43` 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.0b43` 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
|
|
|
@@ -17,12 +17,16 @@ import urllib.error
|
|
|
17
17
|
import urllib.request
|
|
18
18
|
from dataclasses import dataclass
|
|
19
19
|
from pathlib import Path
|
|
20
|
-
from typing import Any, Dict, Mapping, Optional, Pattern, Sequence
|
|
20
|
+
from typing import Any, Callable, Dict, Mapping, Optional, Pattern, Sequence
|
|
21
21
|
from urllib.parse import quote
|
|
22
22
|
import re
|
|
23
23
|
|
|
24
24
|
MIN_ABBREVIATED_SHA_LENGTH = 7
|
|
25
25
|
AUTHOR_EXCLUSION_ENV = "CODE_MOWER_AUTHOR_EXCLUSION_JSON"
|
|
26
|
+
ACTIONS_RUN_MARKER_RE = re.compile(
|
|
27
|
+
r"<!--\s*CODE_MOWER_AUDIT_RUN:\s*run_id=([0-9]+)\s*-->"
|
|
28
|
+
)
|
|
29
|
+
TRUSTED_ACTIONS_AUDIT_EVENTS = frozenset({"pull_request_target"})
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
@dataclass(frozen=True)
|
|
@@ -291,6 +295,130 @@ def github_request_with_fallback(
|
|
|
291
295
|
raise RuntimeError(f"no GitHub tokens available for {method} {path}")
|
|
292
296
|
|
|
293
297
|
|
|
298
|
+
def parse_csv_set(raw: str) -> frozenset[str]:
|
|
299
|
+
return frozenset(item.strip() for item in raw.split(",") if item.strip())
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def parse_audit_run_id(body: str) -> str:
|
|
303
|
+
match = ACTIONS_RUN_MARKER_RE.search(body)
|
|
304
|
+
return match.group(1) if match else ""
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def workflow_path_matches(path: str, trusted_workflows: Sequence[str]) -> bool:
|
|
308
|
+
if not path:
|
|
309
|
+
return False
|
|
310
|
+
return any(path == item or path.endswith("/" + item) for item in trusted_workflows)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _pr_item_matches_head(
|
|
314
|
+
item: Mapping[str, Any],
|
|
315
|
+
*,
|
|
316
|
+
issue_number: int,
|
|
317
|
+
head_sha: str,
|
|
318
|
+
fallback_head_sha: str = "",
|
|
319
|
+
) -> bool:
|
|
320
|
+
if str(item.get("number") or "") != str(issue_number):
|
|
321
|
+
return False
|
|
322
|
+
pr_head = str(((item.get("head") or {}).get("sha")) or "")
|
|
323
|
+
if pr_head:
|
|
324
|
+
return sha_matches(pr_head, head_sha)
|
|
325
|
+
return bool(fallback_head_sha) and sha_matches(fallback_head_sha, head_sha)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def fetch_actions_run(
|
|
329
|
+
repo: str,
|
|
330
|
+
run_id: str,
|
|
331
|
+
*,
|
|
332
|
+
tokens: Sequence[GitHubToken],
|
|
333
|
+
) -> dict[str, Any]:
|
|
334
|
+
response = github_request_with_fallback(
|
|
335
|
+
"GET",
|
|
336
|
+
f"/repos/{repo}/actions/runs/{run_id}",
|
|
337
|
+
tokens=tokens,
|
|
338
|
+
)
|
|
339
|
+
return response if isinstance(response, dict) else {}
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def fetch_pull_requests_for_commit(
|
|
343
|
+
repo: str,
|
|
344
|
+
head_sha: str,
|
|
345
|
+
*,
|
|
346
|
+
tokens: Sequence[GitHubToken],
|
|
347
|
+
) -> list[dict[str, Any]]:
|
|
348
|
+
response = github_request_with_fallback(
|
|
349
|
+
"GET",
|
|
350
|
+
f"/repos/{repo}/commits/{head_sha}/pulls?per_page=100",
|
|
351
|
+
tokens=tokens,
|
|
352
|
+
)
|
|
353
|
+
return response if isinstance(response, list) else []
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def github_actions_comment_attested(
|
|
357
|
+
*,
|
|
358
|
+
repo: str,
|
|
359
|
+
body: str,
|
|
360
|
+
issue_number: int,
|
|
361
|
+
head_sha: str,
|
|
362
|
+
workflow_paths: Sequence[str],
|
|
363
|
+
tokens: Sequence[GitHubToken],
|
|
364
|
+
actions_run_lookup: Optional[Callable[[str], Mapping[str, Any]]] = None,
|
|
365
|
+
commit_pull_requests_lookup: Optional[Callable[[str], Sequence[Mapping[str, Any]]]] = None,
|
|
366
|
+
) -> bool:
|
|
367
|
+
"""Return whether a github-actions[bot] audit comment is tied to this PR head."""
|
|
368
|
+
trusted_workflows = tuple(parse_csv_set(",".join(workflow_paths)))
|
|
369
|
+
token_list = tuple(token for token in tokens if token.value)
|
|
370
|
+
run_id = parse_audit_run_id(body)
|
|
371
|
+
if not trusted_workflows or not token_list or not run_id or not head_sha:
|
|
372
|
+
return False
|
|
373
|
+
try:
|
|
374
|
+
if actions_run_lookup is None:
|
|
375
|
+
run = fetch_actions_run(repo, run_id, tokens=token_list)
|
|
376
|
+
else:
|
|
377
|
+
run = dict(actions_run_lookup(run_id))
|
|
378
|
+
except Exception:
|
|
379
|
+
return False
|
|
380
|
+
|
|
381
|
+
path = str(run.get("path") or "")
|
|
382
|
+
if not workflow_path_matches(path, trusted_workflows):
|
|
383
|
+
return False
|
|
384
|
+
if str(run.get("event") or "") not in TRUSTED_ACTIONS_AUDIT_EVENTS:
|
|
385
|
+
return False
|
|
386
|
+
|
|
387
|
+
run_head_sha = str(run.get("head_sha") or "")
|
|
388
|
+
pull_requests = run.get("pull_requests")
|
|
389
|
+
if isinstance(pull_requests, list) and pull_requests:
|
|
390
|
+
return any(
|
|
391
|
+
isinstance(item, Mapping)
|
|
392
|
+
and _pr_item_matches_head(
|
|
393
|
+
item,
|
|
394
|
+
issue_number=issue_number,
|
|
395
|
+
head_sha=head_sha,
|
|
396
|
+
fallback_head_sha=run_head_sha,
|
|
397
|
+
)
|
|
398
|
+
for item in pull_requests
|
|
399
|
+
)
|
|
400
|
+
if not run_head_sha or not sha_matches(run_head_sha, head_sha):
|
|
401
|
+
return False
|
|
402
|
+
|
|
403
|
+
try:
|
|
404
|
+
if commit_pull_requests_lookup is None:
|
|
405
|
+
associated_prs = fetch_pull_requests_for_commit(repo, run_head_sha, tokens=token_list)
|
|
406
|
+
else:
|
|
407
|
+
associated_prs = commit_pull_requests_lookup(run_head_sha)
|
|
408
|
+
except Exception:
|
|
409
|
+
return False
|
|
410
|
+
return any(
|
|
411
|
+
isinstance(item, Mapping)
|
|
412
|
+
and _pr_item_matches_head(
|
|
413
|
+
item,
|
|
414
|
+
issue_number=issue_number,
|
|
415
|
+
head_sha=head_sha,
|
|
416
|
+
fallback_head_sha=run_head_sha,
|
|
417
|
+
)
|
|
418
|
+
for item in associated_prs
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
|
|
294
422
|
def fetch_pull_request(
|
|
295
423
|
repo: str,
|
|
296
424
|
issue_number: int,
|
|
@@ -36,6 +36,7 @@ if __package__ in {None, "", "tools"}:
|
|
|
36
36
|
fetch_pull_request,
|
|
37
37
|
fetch_base_ref_sha as _shared_fetch_base_ref_sha,
|
|
38
38
|
fetch_pr_head_sha as _shared_fetch_pr_head_sha,
|
|
39
|
+
fetch_pr_head_sha_unless_local_matches as _shared_fetch_pr_head_sha_unless_local_matches,
|
|
39
40
|
limit_comment_body,
|
|
40
41
|
one_line as _one_line,
|
|
41
42
|
parse_repo_paths as _parse_repo_paths,
|
|
@@ -61,6 +62,7 @@ if __package__ in {None, "", "tools"}:
|
|
|
61
62
|
fetch_pull_request,
|
|
62
63
|
fetch_base_ref_sha as _shared_fetch_base_ref_sha,
|
|
63
64
|
fetch_pr_head_sha as _shared_fetch_pr_head_sha,
|
|
65
|
+
fetch_pr_head_sha_unless_local_matches as _shared_fetch_pr_head_sha_unless_local_matches,
|
|
64
66
|
limit_comment_body,
|
|
65
67
|
one_line as _one_line,
|
|
66
68
|
parse_repo_paths as _parse_repo_paths,
|
|
@@ -84,6 +86,7 @@ else: # pragma: no cover - exercised after package extraction.
|
|
|
84
86
|
fetch_base_ref_sha as _shared_fetch_base_ref_sha,
|
|
85
87
|
fetch_pull_request,
|
|
86
88
|
fetch_pr_head_sha as _shared_fetch_pr_head_sha,
|
|
89
|
+
fetch_pr_head_sha_unless_local_matches as _shared_fetch_pr_head_sha_unless_local_matches,
|
|
87
90
|
limit_comment_body,
|
|
88
91
|
one_line as _one_line,
|
|
89
92
|
parse_repo_paths as _parse_repo_paths,
|
|
@@ -109,10 +112,16 @@ MAX_SUMMARY_CHARS = 4_000
|
|
|
109
112
|
MAX_FINDING_TITLE_CHARS = 300
|
|
110
113
|
MAX_FINDING_FILE_CHARS = 500
|
|
111
114
|
MAX_FINDING_DETAIL_CHARS = 4_000
|
|
115
|
+
MAX_CLAUDE_AUDIT_ATTEMPTS = 2
|
|
116
|
+
MIN_BLOCKED_VERDICT_PROSE_CHARS = 80
|
|
117
|
+
SCHEMA_PLACEHOLDER_TEXT = frozenset({"test", "example", "placeholder"})
|
|
118
|
+
CLAUDE_RAW_OUTPUT_SCHEMA = "code_mower.claudeAuditRawOutput.v1"
|
|
119
|
+
BLOCKER_SEVERITIES = frozenset({"P0", "P1", "P2"})
|
|
112
120
|
|
|
113
121
|
STALE_TRAILER = "<!-- CLAUDE_AUDIT_STATE: needs-claude-audit -->"
|
|
114
122
|
DONE_TRAILER = "<!-- CLAUDE_AUDIT_STATE: claude-audit-done -->"
|
|
115
123
|
BLOCKED_TRAILER = "<!-- CLAUDE_AUDIT_STATE: claude-audit-blocked -->"
|
|
124
|
+
AUDIT_RUN_TRAILER_PREFIX = "<!-- CODE_MOWER_AUDIT_RUN:"
|
|
116
125
|
|
|
117
126
|
|
|
118
127
|
CLAUDE_VERDICT_SCHEMA: Dict[str, Any] = {
|
|
@@ -177,6 +186,8 @@ class ClaudeVerdict:
|
|
|
177
186
|
p2_count: int = 0
|
|
178
187
|
p3_count: int = 0
|
|
179
188
|
mismatch_note: str = ""
|
|
189
|
+
summary: str = ""
|
|
190
|
+
findings: Tuple[Dict[str, Any], ...] = field(default_factory=tuple)
|
|
180
191
|
|
|
181
192
|
@property
|
|
182
193
|
def blocker_count(self) -> int:
|
|
@@ -203,6 +214,7 @@ class ClaudeAuditResult:
|
|
|
203
214
|
class DiffContext:
|
|
204
215
|
stat: str
|
|
205
216
|
diff: str
|
|
217
|
+
changed_files: Tuple[str, ...]
|
|
206
218
|
was_truncated: bool
|
|
207
219
|
requested_max_bytes: int
|
|
208
220
|
hard_limit_bytes: int
|
|
@@ -333,7 +345,7 @@ def parse_structured_claude_verdict(data: Any) -> ClaudeVerdict:
|
|
|
333
345
|
|
|
334
346
|
p_counts[int(severity[1])] += 1
|
|
335
347
|
if len(rendered_findings) < MAX_RENDERED_FINDINGS:
|
|
336
|
-
rendered_findings.append(raw_finding)
|
|
348
|
+
rendered_findings.append(dict(raw_finding))
|
|
337
349
|
|
|
338
350
|
blocker_count = p_counts[0] + p_counts[1] + p_counts[2]
|
|
339
351
|
if blocker_count > 0:
|
|
@@ -358,9 +370,139 @@ def parse_structured_claude_verdict(data: Any) -> ClaudeVerdict:
|
|
|
358
370
|
p2_count=p_counts[2],
|
|
359
371
|
p3_count=p_counts[3],
|
|
360
372
|
mismatch_note=mismatch_note,
|
|
373
|
+
summary=summary,
|
|
374
|
+
findings=tuple(dict(finding) for finding in raw_findings),
|
|
361
375
|
)
|
|
362
376
|
|
|
363
377
|
|
|
378
|
+
def _placeholder_equal(value: object) -> bool:
|
|
379
|
+
return str(value or "").strip().lower() in SCHEMA_PLACEHOLDER_TEXT
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _normalize_diff_path(value: object) -> str:
|
|
383
|
+
path = str(value or "").strip().replace("\\", "/")
|
|
384
|
+
while path.startswith("./"):
|
|
385
|
+
path = path[2:]
|
|
386
|
+
if path.startswith(("a/", "b/")):
|
|
387
|
+
path = path[2:]
|
|
388
|
+
return path
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _diff_file_matches(file_path: str, allowed_files: set[str]) -> bool:
|
|
392
|
+
if file_path in allowed_files:
|
|
393
|
+
return True
|
|
394
|
+
suffix_matches = [
|
|
395
|
+
path
|
|
396
|
+
for path in allowed_files
|
|
397
|
+
if path.endswith(f"/{file_path}") or file_path.endswith(f"/{path}")
|
|
398
|
+
]
|
|
399
|
+
return len(suffix_matches) == 1
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _claude_verdict_guardrail_reason(
|
|
403
|
+
parsed: ClaudeVerdict,
|
|
404
|
+
changed_files: Tuple[str, ...],
|
|
405
|
+
) -> Optional[str]:
|
|
406
|
+
if parsed.verdict == "UNKNOWN":
|
|
407
|
+
return None
|
|
408
|
+
|
|
409
|
+
if _placeholder_equal(parsed.summary):
|
|
410
|
+
return "structured verdict summary matched a schema placeholder value"
|
|
411
|
+
|
|
412
|
+
allowed_files = {_normalize_diff_path(path) for path in changed_files}
|
|
413
|
+
allowed_files.discard("")
|
|
414
|
+
for idx, finding in enumerate(parsed.findings):
|
|
415
|
+
where = f"findings[{idx}]"
|
|
416
|
+
if _placeholder_equal(finding.get("title")):
|
|
417
|
+
return f"{where}.title matched a schema placeholder value"
|
|
418
|
+
if _placeholder_equal(finding.get("detail")):
|
|
419
|
+
return f"{where}.detail matched a schema placeholder value"
|
|
420
|
+
|
|
421
|
+
file_path = _normalize_diff_path(finding.get("file"))
|
|
422
|
+
severity = str(finding.get("severity") or "")
|
|
423
|
+
if (
|
|
424
|
+
severity in BLOCKER_SEVERITIES
|
|
425
|
+
and (not file_path or not _diff_file_matches(file_path, allowed_files))
|
|
426
|
+
):
|
|
427
|
+
return f"{where}.file {file_path!r} is not present in the PR diff"
|
|
428
|
+
|
|
429
|
+
if (
|
|
430
|
+
parsed.verdict == "BLOCKED"
|
|
431
|
+
and len(parsed.prose.strip()) < MIN_BLOCKED_VERDICT_PROSE_CHARS
|
|
432
|
+
):
|
|
433
|
+
return "structured blocked verdict body is too short to be credible"
|
|
434
|
+
|
|
435
|
+
return None
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _apply_claude_verdict_guardrails(
|
|
439
|
+
parsed: ClaudeVerdict,
|
|
440
|
+
changed_files: Tuple[str, ...],
|
|
441
|
+
) -> Tuple[ClaudeVerdict, Optional[str]]:
|
|
442
|
+
reason = _claude_verdict_guardrail_reason(parsed, changed_files)
|
|
443
|
+
if reason is None:
|
|
444
|
+
return parsed, None
|
|
445
|
+
return _unknown_structured_verdict(reason), reason
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _write_claude_raw_output_sidecar(
|
|
449
|
+
artifact_path: Optional[Path],
|
|
450
|
+
attempts: List[Dict[str, Any]],
|
|
451
|
+
) -> None:
|
|
452
|
+
if artifact_path is None:
|
|
453
|
+
return
|
|
454
|
+
sidecar_path = artifact_path.with_suffix(".claude-raw-output.json")
|
|
455
|
+
payload = {"schema": CLAUDE_RAW_OUTPUT_SCHEMA, "attempts": attempts}
|
|
456
|
+
try:
|
|
457
|
+
text = json.dumps(payload, indent=2, sort_keys=True) + "\n"
|
|
458
|
+
sidecar_path.write_text(text, encoding="utf-8")
|
|
459
|
+
except OSError as exc:
|
|
460
|
+
print(
|
|
461
|
+
f"warning: failed to write Claude raw output sidecar {sidecar_path}: {exc}",
|
|
462
|
+
file=sys.stderr,
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _guardrail_retry_prompt(
|
|
467
|
+
prompt: str,
|
|
468
|
+
reason: str,
|
|
469
|
+
changed_files: Tuple[str, ...],
|
|
470
|
+
) -> str:
|
|
471
|
+
files = "\n".join(f"- {path}" for path in changed_files[:50])
|
|
472
|
+
if len(changed_files) > 50:
|
|
473
|
+
files += f"\n- ... {len(changed_files) - 50} additional file(s) omitted"
|
|
474
|
+
return (
|
|
475
|
+
f"{prompt.rstrip()}\n\n"
|
|
476
|
+
"Trusted wrapper retry instruction:\n"
|
|
477
|
+
f"The previous structured verdict was rejected: {reason}. "
|
|
478
|
+
"Return a fresh JSON verdict only. Do not use placeholder values such "
|
|
479
|
+
"as 'test', 'example', or 'placeholder'. P0/P1/P2 findings must cite a "
|
|
480
|
+
"file from the PR diff, using the repository-relative path or a unique "
|
|
481
|
+
"suffix. PR diff files:\n"
|
|
482
|
+
f"{files}\n"
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _record_posted_comment_url(
|
|
487
|
+
artifact_path: Optional[Path],
|
|
488
|
+
posted_comment_url: Optional[str],
|
|
489
|
+
) -> None:
|
|
490
|
+
if artifact_path is None or not posted_comment_url:
|
|
491
|
+
return
|
|
492
|
+
try:
|
|
493
|
+
payload = json.loads(artifact_path.read_text(encoding="utf-8"))
|
|
494
|
+
if not isinstance(payload, dict):
|
|
495
|
+
return
|
|
496
|
+
payload["posted_comment_url"] = posted_comment_url
|
|
497
|
+
text = json.dumps(payload, indent=2, sort_keys=True) + "\n"
|
|
498
|
+
artifact_path.write_text(text, encoding="utf-8")
|
|
499
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
500
|
+
print(
|
|
501
|
+
f"warning: failed to update audit verdict artifact {artifact_path}: {exc}",
|
|
502
|
+
file=sys.stderr,
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
|
|
364
506
|
def _claude_env() -> Dict[str, str]:
|
|
365
507
|
preserve_auth_overrides = env_flag(os.environ.get("CODE_MOWER_CLAUDE_KEEP_AUTH_ENV"))
|
|
366
508
|
env, _removed = clean_claude_cli_env(
|
|
@@ -387,7 +529,17 @@ def _fetch_base_sha_for_diff(local_repo: Path, base_ref: str) -> str:
|
|
|
387
529
|
return _shared_fetch_base_ref_sha(local_repo, base_ref)
|
|
388
530
|
|
|
389
531
|
|
|
390
|
-
def _fetch_pr_head_sha_for_diff(
|
|
532
|
+
def _fetch_pr_head_sha_for_diff(
|
|
533
|
+
local_repo: Path,
|
|
534
|
+
pr_number: int,
|
|
535
|
+
expected_head_sha: str | None = None,
|
|
536
|
+
) -> str:
|
|
537
|
+
if expected_head_sha:
|
|
538
|
+
return _shared_fetch_pr_head_sha_unless_local_matches(
|
|
539
|
+
local_repo,
|
|
540
|
+
pr_number,
|
|
541
|
+
expected_head_sha=expected_head_sha,
|
|
542
|
+
)
|
|
391
543
|
return _shared_fetch_pr_head_sha(local_repo, pr_number)
|
|
392
544
|
|
|
393
545
|
|
|
@@ -482,11 +634,22 @@ def _build_diff_context(
|
|
|
482
634
|
)
|
|
483
635
|
|
|
484
636
|
fetched_base_ref = _fetch_base_sha_for_diff(local_repo, base_ref)
|
|
485
|
-
fetched_head_ref = _fetch_pr_head_sha_for_diff(
|
|
637
|
+
fetched_head_ref = _fetch_pr_head_sha_for_diff(
|
|
638
|
+
local_repo,
|
|
639
|
+
pr_number,
|
|
640
|
+
expected_head_sha=expected_head_sha,
|
|
641
|
+
)
|
|
486
642
|
if fetched_head_ref.lower() != expected_head_sha.lower():
|
|
487
643
|
raise FetchedHeadMismatch(expected_head_sha, fetched_head_ref)
|
|
488
644
|
diff_range = f"{fetched_base_ref}...{fetched_head_ref}"
|
|
489
645
|
stat = _run_git(local_repo, ["diff", "--stat", "--find-renames", diff_range])
|
|
646
|
+
changed_files_text = _run_git(
|
|
647
|
+
local_repo,
|
|
648
|
+
["-c", "core.quotePath=false", "diff", "--name-only", "--find-renames", diff_range],
|
|
649
|
+
)
|
|
650
|
+
changed_files = tuple(
|
|
651
|
+
line.strip() for line in changed_files_text.splitlines() if line.strip()
|
|
652
|
+
)
|
|
490
653
|
included_diff, full_diff_bytes, was_truncated = _run_git_limited(
|
|
491
654
|
local_repo,
|
|
492
655
|
["diff", "--find-renames", "--unified=80", diff_range],
|
|
@@ -500,6 +663,7 @@ def _build_diff_context(
|
|
|
500
663
|
return DiffContext(
|
|
501
664
|
stat=stat,
|
|
502
665
|
diff=included_diff,
|
|
666
|
+
changed_files=changed_files,
|
|
503
667
|
was_truncated=was_truncated,
|
|
504
668
|
requested_max_bytes=max_diff_bytes,
|
|
505
669
|
hard_limit_bytes=hard_limit,
|
|
@@ -705,10 +869,13 @@ def format_comment(
|
|
|
705
869
|
stale_end_sha: Optional[str] = None,
|
|
706
870
|
is_unknown: bool = False,
|
|
707
871
|
merge_authority: bool = True,
|
|
872
|
+
actions_run_id: Optional[str] = None,
|
|
708
873
|
) -> str:
|
|
709
874
|
posture = "merge-authority lane" if merge_authority else "informational only"
|
|
710
875
|
header = f"## Claude audit ({posture})\n\n"
|
|
711
876
|
header += f"Head SHA: `{head_sha}`\n"
|
|
877
|
+
if actions_run_id:
|
|
878
|
+
header += f"{AUDIT_RUN_TRAILER_PREFIX} run_id={actions_run_id} -->\n"
|
|
712
879
|
if is_stale:
|
|
713
880
|
body = (
|
|
714
881
|
header
|
|
@@ -848,6 +1015,10 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
|
|
|
848
1015
|
)
|
|
849
1016
|
posted = post_pr_comment(repo, pr_number, comment_body, token=config.github_token)
|
|
850
1017
|
result.posted_comment_url = posted.get("html_url")
|
|
1018
|
+
_record_posted_comment_url(
|
|
1019
|
+
result.verdict_artifact_path,
|
|
1020
|
+
result.posted_comment_url,
|
|
1021
|
+
)
|
|
851
1022
|
print(
|
|
852
1023
|
f"posted {repo}#{pr_number} verdict=STALE "
|
|
853
1024
|
f"url={result.posted_comment_url}",
|
|
@@ -906,8 +1077,46 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
|
|
|
906
1077
|
),
|
|
907
1078
|
)
|
|
908
1079
|
|
|
1080
|
+
raw_output_attempts: List[Dict[str, Any]] = []
|
|
1081
|
+
parsed = _unknown_structured_verdict("Claude audit did not run")
|
|
1082
|
+
claude_stdout = ""
|
|
1083
|
+
claude_stderr = ""
|
|
1084
|
+
attempt_prompt = prompt
|
|
909
1085
|
t0 = time.time()
|
|
910
|
-
|
|
1086
|
+
for attempt in range(1, MAX_CLAUDE_AUDIT_ATTEMPTS + 1):
|
|
1087
|
+
parsed_candidate, attempt_stdout, attempt_stderr = run_claude_audit(
|
|
1088
|
+
config,
|
|
1089
|
+
attempt_prompt,
|
|
1090
|
+
)
|
|
1091
|
+
claude_stdout = attempt_stdout
|
|
1092
|
+
claude_stderr = attempt_stderr
|
|
1093
|
+
parsed, guardrail_reason = _apply_claude_verdict_guardrails(
|
|
1094
|
+
parsed_candidate,
|
|
1095
|
+
diff_context.changed_files,
|
|
1096
|
+
)
|
|
1097
|
+
raw_output_attempts.append(
|
|
1098
|
+
{
|
|
1099
|
+
"attempt": attempt,
|
|
1100
|
+
"stdout": attempt_stdout,
|
|
1101
|
+
"stderr": attempt_stderr,
|
|
1102
|
+
"verdict": parsed_candidate.verdict,
|
|
1103
|
+
"guardrail_rejection": guardrail_reason,
|
|
1104
|
+
}
|
|
1105
|
+
)
|
|
1106
|
+
if guardrail_reason and attempt < MAX_CLAUDE_AUDIT_ATTEMPTS:
|
|
1107
|
+
print(
|
|
1108
|
+
" structured-verdict guardrail rejected Claude output: "
|
|
1109
|
+
f"{guardrail_reason}; retrying once",
|
|
1110
|
+
file=sys.stderr,
|
|
1111
|
+
flush=True,
|
|
1112
|
+
)
|
|
1113
|
+
attempt_prompt = _guardrail_retry_prompt(
|
|
1114
|
+
prompt,
|
|
1115
|
+
guardrail_reason,
|
|
1116
|
+
diff_context.changed_files,
|
|
1117
|
+
)
|
|
1118
|
+
continue
|
|
1119
|
+
break
|
|
911
1120
|
dt = time.time() - t0
|
|
912
1121
|
print(f" claude audit completed in {dt:.0f}s", file=sys.stderr, flush=True)
|
|
913
1122
|
if parsed.mismatch_note:
|
|
@@ -916,6 +1125,7 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
|
|
|
916
1125
|
pr_meta_after = fetch_pull_request(repo, pr_number, token=config.github_token)
|
|
917
1126
|
head_sha_end = pr_meta_after["head"]["sha"]
|
|
918
1127
|
is_stale = head_sha_start != head_sha_end
|
|
1128
|
+
actions_run_id = os.environ.get("GITHUB_RUN_ID") or None
|
|
919
1129
|
|
|
920
1130
|
if is_stale:
|
|
921
1131
|
comment_body = format_comment(
|
|
@@ -924,6 +1134,7 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
|
|
|
924
1134
|
is_stale=True,
|
|
925
1135
|
stale_end_sha=head_sha_end,
|
|
926
1136
|
merge_authority=config.merge_authority,
|
|
1137
|
+
actions_run_id=actions_run_id,
|
|
927
1138
|
)
|
|
928
1139
|
result_verdict = "STALE"
|
|
929
1140
|
trailer = STALE_TRAILER
|
|
@@ -933,6 +1144,7 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
|
|
|
933
1144
|
head_sha_start,
|
|
934
1145
|
is_unknown=True,
|
|
935
1146
|
merge_authority=config.merge_authority,
|
|
1147
|
+
actions_run_id=actions_run_id,
|
|
936
1148
|
)
|
|
937
1149
|
result_verdict = "UNKNOWN"
|
|
938
1150
|
trailer = STALE_TRAILER
|
|
@@ -941,6 +1153,7 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
|
|
|
941
1153
|
parsed,
|
|
942
1154
|
head_sha_start,
|
|
943
1155
|
merge_authority=config.merge_authority,
|
|
1156
|
+
actions_run_id=actions_run_id,
|
|
944
1157
|
)
|
|
945
1158
|
result_verdict = parsed.verdict
|
|
946
1159
|
trailer = BLOCKED_TRAILER if parsed.verdict == "BLOCKED" else DONE_TRAILER
|
|
@@ -976,8 +1189,13 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
|
|
|
976
1189
|
file=sys.stderr,
|
|
977
1190
|
flush=True,
|
|
978
1191
|
)
|
|
1192
|
+
_write_claude_raw_output_sidecar(artifact_path, raw_output_attempts)
|
|
979
1193
|
posted = post_pr_comment(repo, pr_number, comment_body, token=config.github_token)
|
|
980
1194
|
result.posted_comment_url = posted.get("html_url")
|
|
1195
|
+
_record_posted_comment_url(
|
|
1196
|
+
result.verdict_artifact_path,
|
|
1197
|
+
result.posted_comment_url,
|
|
1198
|
+
)
|
|
981
1199
|
print(
|
|
982
1200
|
f"posted {repo}#{pr_number} verdict={result_verdict} "
|
|
983
1201
|
f"url={result.posted_comment_url}",
|
|
@@ -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.0b43`, or run source checkouts with "
|
|
18
18
|
"`PYTHONPATH=src python -m code_mower.cli`."
|
|
19
19
|
)
|
|
20
20
|
|
|
@@ -418,6 +418,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
418
418
|
default=MAX_EVENT_COUNT,
|
|
419
419
|
help=f"number of verdict artifacts to include; max {MAX_EVENT_COUNT}",
|
|
420
420
|
)
|
|
421
|
+
reviewer_runs.add_argument(
|
|
422
|
+
"--offset",
|
|
423
|
+
type=int,
|
|
424
|
+
default=0,
|
|
425
|
+
help="skip this many newest matching verdict artifacts before applying --limit",
|
|
426
|
+
)
|
|
421
427
|
reviewer_runs.add_argument(
|
|
422
428
|
"--include-git-ref",
|
|
423
429
|
action="store_true",
|
|
@@ -674,6 +680,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
674
680
|
team_id=args.team_id,
|
|
675
681
|
install_id=args.install_id,
|
|
676
682
|
limit=args.limit,
|
|
683
|
+
offset=args.offset,
|
|
677
684
|
endpoint=args.endpoint,
|
|
678
685
|
token_env=args.token_env,
|
|
679
686
|
yes=args.yes,
|