code-mower 0.5.0b46__tar.gz → 0.5.0b48__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.0b46 → code_mower-0.5.0b48}/PKG-INFO +6 -6
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/README.md +5 -5
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/pyproject.toml +1 -1
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/__init__.py +1 -1
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/audit_labeler_lib.py +236 -6
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/__init__.py +2 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/effect_report.py +10 -2
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/evidence_report.py +5 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/policy.py +2 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/run_status.py +4 -1
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/value_report.py +7 -5
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/claude_audit_pr.py +197 -5
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/cli.py +1 -1
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/code_mower_calibration.py +5 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/code_mower_telemetry.py +2 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/codex_audit_pr.py +80 -14
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/__init__.py +2 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/github_actions_failures.py +26 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/runner.py +2 -0
- code_mower-0.5.0b48/src/code_mower/doctor_checks/runtime.py +173 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/gate_health.py +401 -21
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/init.py +36 -2
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/package_content.py +3 -4
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/package_manifest.py +1 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/provider_runners/__init__.py +8 -1
- code_mower-0.5.0b48/src/code_mower/provider_runners/comments.py +71 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/provider_runners/github_pr.py +15 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/provider_runners/verdict_artifacts.py +12 -4
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/reviewer_metrics.py +3 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/product-support/safe_gh_comment.py +21 -1
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/reviewer-value-report.example.md +3 -3
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/workflows/builder-provenance.yml.j2 +1 -1
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/workflows/code-mower-gate-health.yml.j2 +7 -0
- code_mower-0.5.0b48/src/code_mower/templates/workflows/code-mower-gate.yml.j2 +612 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/workflows/needs-owner-notify.yml.j2 +3 -3
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/workflows/review-clear-stale.yml.j2 +2 -3
- {code_mower-0.5.0b46 → code_mower-0.5.0b48/src/code_mower}/templates/workflows/self-hosted-local-audit.yml.j2 +46 -55
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/workflows/trailer-comment-labeler.yml.j2 +15 -2
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/trailer_comment_labeler.py +107 -1
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower.egg-info/PKG-INFO +6 -6
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower.egg-info/SOURCES.txt +1 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/reviewer-value-report.example.md +3 -3
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/workflows/code-mower-gate-health.yml.j2 +7 -0
- code_mower-0.5.0b48/templates/workflows/code-mower-gate.yml.j2 +612 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/workflows/review-clear-stale.yml.j2 +2 -3
- {code_mower-0.5.0b46/src/code_mower → code_mower-0.5.0b48}/templates/workflows/self-hosted-local-audit.yml.j2 +46 -55
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/workflows/trailer-comment-labeler.yml.j2 +15 -2
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_audit_comment_posture.py +31 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_builder_identity.py +21 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_calibration_effect_report.py +33 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_calibration_truth.py +41 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_claude_audit_pr.py +127 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_codex_audit_pr.py +75 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_doctor_github_checks.py +30 -0
- code_mower-0.5.0b48/tests/test_doctor_runtime.py +60 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_gate_health.py +478 -3
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_release_hygiene.py +754 -54
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_reviewer_metrics_core.py +64 -0
- code_mower-0.5.0b48/tests/test_trailer_comment_labeler.py +339 -0
- code_mower-0.5.0b46/src/code_mower/doctor_checks/runtime.py +0 -89
- code_mower-0.5.0b46/src/code_mower/provider_runners/comments.py +0 -31
- code_mower-0.5.0b46/src/code_mower/templates/workflows/code-mower-gate.yml.j2 +0 -269
- code_mower-0.5.0b46/templates/workflows/code-mower-gate.yml.j2 +0 -269
- code_mower-0.5.0b46/tests/test_trailer_comment_labeler.py +0 -151
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/CODE_OF_CONDUCT.md +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/LICENSE +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/MANIFEST.in +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/NOTICE +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/SUPPORT.md +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/requirements/requirements.txt +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/setup.cfg +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/adapters/__init__.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/adapters/_base.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/adapters/cursor_bugbot.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/adapters/gitar.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/adapters/greptile.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/adapters/qodo.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/antigravity_cli_audit_pr.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/audit_handoff_log.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/audit_progress.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/blind_review_artifacts.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/blind_review_coordinator.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/bootstrap.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/builder_experiment.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/builder_runs.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/arms.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/auto_discovery.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/commands.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/context_inputs.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/corpus.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/evidence.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/identity.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/metrics.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/overlap.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/planning.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/results.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/run_results.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/runner.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/calibration/truth.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/checks.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/claude_cli_bounce.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/claude_cli_environment.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/clear_stale.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/cloud.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/cloud_client/__init__.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/cloud_client/bundle.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/cloud_client/doctor.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/cloud_client/dogfood.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/cloud_client/endpoints.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/cloud_client/errors.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/cloud_client/events.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/cloud_client/export.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/cloud_client/git_metadata.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/cloud_client/manifest.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/cloud_client/operations.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/cloud_client/reports.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/cloud_client/setup.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/cloud_client/upload.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/code_mower_context_packs.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/code_mower_merge.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/coderabbit_cli_audit_pr.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/codex_audit_env_preflight.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/codex_audit_schema_smoke.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/codex_audit_verdict.schema.json +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/config.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/cloud.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/common.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/github.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/github_actions.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/github_actions_cost.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/github_actions_cost_summary.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/github_actions_failure_annotations.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/github_actions_failure_models.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/github_actions_failure_scan.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/github_actions_failure_selection.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/github_actions_permissions.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/github_api.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/github_branch.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/github_config.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/github_provider.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/github_repo.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/groups.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/models.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/output.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/presets.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/privacy.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/provider_api_model.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/provider_api_model_openai.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/provider_api_model_profiles.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/provider_env.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/provider_env_required.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/provider_env_tokens.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/provider_local_cli.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/provider_local_cli_commands.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/provider_local_cli_probe_config.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/provider_probe.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/provider_probe_auth.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/provider_probe_evaluation.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/provider_probe_json.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/provider_probe_remediation.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/provider_review_hygiene.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/providers.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/registry.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/doctor_checks/runtime_github_auth.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/gemini_cli_audit_pr.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/grok_build_audit_pr.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/hermes_cli_audit_pr.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/lane_configs/__init__.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/lane_configs/aider.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/lane_configs/antigravity_cli.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/lane_configs/claude.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/lane_configs/codex.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/lane_configs/devin.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/lane_configs/gemini_cli.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/lane_configs/grok_build.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/lane_configs/hermes_cli.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/lane_configs/local_llm.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/local_llm_audit_pr.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/local_llm_bakeoff.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/local_llm_calibration.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/local_llm_profiles.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/migration.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/migration_install.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/migration_mirror.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/migration_readiness.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/migration_rehearsal.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/next_steps.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/package.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/package_paths.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/package_rendering.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/package_static.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/plan_context.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/prompts.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/provider_registry.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/provider_runners/git.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/provider_runners/github_auth.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/provider_runners/pr_worktree.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/provider_runners/process.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/provider_runners/repo_paths.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/provider_runners/text_schema.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/provider_runners/workspace.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/providers/__init__.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/providers/local_cli.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/providers/provenance.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/release_readiness.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/reviewer_spend.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/saas_reviewer_labeler.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/secrets.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/builder-experiment.example.json +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/calibration-corpus.example.json +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/calibration-corpus.json +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/code-mower.example.yml +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/context-packs.example.json +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/lane_prompts/base-audit.md +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/lane_prompts/calibration-policy.md +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/lane_prompts/context-driven-quality.md +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/lane_prompts/docs-design.md +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/lane_prompts/generic-programming.md +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/lane_prompts/operability.md +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/lane_prompts/package-runtime.md +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/lane_prompts/plan-conformance.md +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/lane_prompts/security-threat-model.md +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/product-support/code_mower +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/product-support/code_mower_standalone_pin.env +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/product-support/code_mower_standalone_shadow.sh +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/product-support/run_claude_audit_pr.sh +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/product-support/run_codex_audit_pr.sh +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/product-support/status_report.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/providers.yml +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/reviewer-spend.example.json +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/workflows/audit-label-cleanup.yml.j2 +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/workflows/hosted-bridge.yml.j2 +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/workflows/local-cli-audit.yml.j2 +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/workflows/private-standalone-shadow.yml.j2 +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/workflows/saas-reviewer-labeler.yml.j2 +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/templates/workflows/weekly-status.yml.j2 +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/versioning.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower/work_orders.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower.egg-info/dependency_links.txt +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower.egg-info/entry_points.txt +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower.egg-info/requires.txt +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/src/code_mower.egg-info/top_level.txt +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/builder-experiment.example.json +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/calibration-corpus.example.json +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/calibration-corpus.json +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/code-mower.yml.j2 +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/context-packs.example.json +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/cursor/BUGBOT.md +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/providers/acp_bridge.yml +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/providers/aider.yml +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/providers/antigravity_cli.yml +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/providers/claude_audit.yml +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/providers/claude_review.yml +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/providers/coderabbit_cli.yml +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/providers/codex.yml +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/providers/cursor_bugbot.yml +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/providers/devin.yml +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/providers/gemini_cli.yml +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/providers/gitar.yml +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/providers/greptile.yml +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/providers/hermes_cli.yml +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/providers/local_llm.yml +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/providers/qodo.yml +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/providers.yml +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/reviewer-spend.example.json +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/workflows/audit-label-cleanup.yml.j2 +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/workflows/blind-review-artifacts-dry-run.yml.j2 +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/workflows/builder-provenance.yml.j2 +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/workflows/hosted-bridge.yml.j2 +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/workflows/local-cli-audit.yml.j2 +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/workflows/private-standalone-shadow.yml.j2 +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/templates/workflows/saas-reviewer-labeler.yml.j2 +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_builder_runs.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_checks.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_claude_cli_bounce.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_clear_stale.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_cloud_client.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_cloud_git_metadata.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_cloud_manifest.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_cloud_reports.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_coderabbit_cli_audit_pr.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_codex_audit_env_preflight.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_doctor_actions_cost_summary.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_doctor_actions_failure_selection.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_doctor_boundaries.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_doctor_output.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_doctor_privacy.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_doctor_provider_env_required.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_doctor_provider_env_tokens.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_doctor_provider_probe.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_doctor_provider_review_hygiene.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_doctor_registry.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_local_llm_github_helpers.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_plan_context.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_prompts.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_provider_local_cli.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_provider_runners_comments.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_provider_runners_git.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_provider_runners_github_pr.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_provider_runners_pr_worktree.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_provider_runners_process.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_provider_runners_repo_paths.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_provider_runners_text_schema.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_provider_runners_workspace.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/tests/test_reviewer_spend.py +0 -0
- {code_mower-0.5.0b46 → code_mower-0.5.0b48}/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.0b48
|
|
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.0b48
|
|
107
107
|
code-mower --version
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
`0.5.
|
|
110
|
+
`0.5.0b48` 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.0b48`.
|
|
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.48` 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.0b48` 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.0b48
|
|
92
92
|
code-mower --version
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
`0.5.
|
|
95
|
+
`0.5.0b48` 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.0b48`.
|
|
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.48` 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.0b48` 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,9 +25,15 @@ 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"})
|
|
33
|
+
AUDIT_RUN_NON_TERMINAL_STATUSES = frozenset(
|
|
34
|
+
{"queued", "requested", "waiting", "pending", "in_progress"}
|
|
35
|
+
)
|
|
36
|
+
HEAD_SHA_LINE_RE = re.compile(r"Head SHA:\s*`?([0-9a-fA-F]{7,40})`?", re.IGNORECASE)
|
|
30
37
|
|
|
31
38
|
|
|
32
39
|
@dataclass(frozen=True)
|
|
@@ -44,6 +51,13 @@ class GitHubToken:
|
|
|
44
51
|
value: str
|
|
45
52
|
|
|
46
53
|
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class ActionsRunMarker:
|
|
56
|
+
run_id: str
|
|
57
|
+
comment_id: str
|
|
58
|
+
body_sha256: str
|
|
59
|
+
|
|
60
|
+
|
|
47
61
|
@dataclass(frozen=True)
|
|
48
62
|
class LaneConfig:
|
|
49
63
|
name: str
|
|
@@ -300,8 +314,32 @@ def parse_csv_set(raw: str) -> frozenset[str]:
|
|
|
300
314
|
|
|
301
315
|
|
|
302
316
|
def parse_audit_run_id(body: str) -> str:
|
|
317
|
+
marker = parse_audit_run_marker(body)
|
|
318
|
+
return marker.run_id if marker else ""
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def parse_audit_run_marker(body: str) -> Optional[ActionsRunMarker]:
|
|
303
322
|
match = ACTIONS_RUN_MARKER_RE.search(body)
|
|
304
|
-
|
|
323
|
+
if not match:
|
|
324
|
+
return None
|
|
325
|
+
return ActionsRunMarker(
|
|
326
|
+
run_id=match.group(1),
|
|
327
|
+
comment_id=match.group(2) or "",
|
|
328
|
+
body_sha256=match.group(3) or "",
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def audit_run_marker_body_digest_matches(body: str) -> bool:
|
|
333
|
+
match = ACTIONS_RUN_MARKER_RE.search(body)
|
|
334
|
+
if not match or not match.group(2) or not match.group(3):
|
|
335
|
+
return False
|
|
336
|
+
canonical_marker = (
|
|
337
|
+
"<!-- CODE_MOWER_AUDIT_RUN: "
|
|
338
|
+
f"run_id={match.group(1)} comment_id={match.group(2)} -->"
|
|
339
|
+
)
|
|
340
|
+
canonical_body = body[: match.start()] + canonical_marker + body[match.end() :]
|
|
341
|
+
digest = hashlib.sha256(canonical_body.encode("utf-8")).hexdigest()
|
|
342
|
+
return digest == match.group(3)
|
|
305
343
|
|
|
306
344
|
|
|
307
345
|
def workflow_path_matches(path: str, trusted_workflows: Sequence[str]) -> bool:
|
|
@@ -310,6 +348,192 @@ def workflow_path_matches(path: str, trusted_workflows: Sequence[str]) -> bool:
|
|
|
310
348
|
return any(path == item or path.endswith("/" + item) for item in trusted_workflows)
|
|
311
349
|
|
|
312
350
|
|
|
351
|
+
def flatten_paginated_items(payload: Any) -> list[dict[str, Any]]:
|
|
352
|
+
items: list[dict[str, Any]] = []
|
|
353
|
+
if isinstance(payload, list):
|
|
354
|
+
for page in payload:
|
|
355
|
+
if isinstance(page, list):
|
|
356
|
+
items.extend(item for item in page if isinstance(item, dict))
|
|
357
|
+
elif isinstance(page, dict):
|
|
358
|
+
items.append(page)
|
|
359
|
+
return items
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def audit_comment_head_sha(body: str) -> str:
|
|
363
|
+
match = HEAD_SHA_LINE_RE.search(body)
|
|
364
|
+
return match.group(1) if match else ""
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def terminal_audit_trailer_verdict(lane: Mapping[str, Any], body: str) -> str:
|
|
368
|
+
candidates: list[tuple[int, str]] = []
|
|
369
|
+
for label_key, verdict in (("done", "done"), ("blocked", "blocked")):
|
|
370
|
+
label = str(lane.get(label_key) or "")
|
|
371
|
+
if not label:
|
|
372
|
+
continue
|
|
373
|
+
index = body.rfind(": " + label + " -->")
|
|
374
|
+
if index >= 0:
|
|
375
|
+
candidates.append((index, verdict))
|
|
376
|
+
if not candidates:
|
|
377
|
+
return ""
|
|
378
|
+
return max(candidates, key=lambda item: item[0])[1]
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def current_audit_trailer_verdict(
|
|
382
|
+
lane: Mapping[str, Any],
|
|
383
|
+
body: str,
|
|
384
|
+
*,
|
|
385
|
+
head_sha: str,
|
|
386
|
+
) -> str:
|
|
387
|
+
if "Head SHA: `" + head_sha + "`" not in body:
|
|
388
|
+
return ""
|
|
389
|
+
return terminal_audit_trailer_verdict(lane, body)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def latest_current_audit_verdict(
|
|
393
|
+
lane: Mapping[str, Any],
|
|
394
|
+
comments: Sequence[Mapping[str, Any]],
|
|
395
|
+
*,
|
|
396
|
+
head_sha: str,
|
|
397
|
+
trusted_comment_author: Callable[
|
|
398
|
+
[Mapping[str, Any], str, str, object, str | None],
|
|
399
|
+
bool,
|
|
400
|
+
],
|
|
401
|
+
) -> str:
|
|
402
|
+
verdicts: list[tuple[tuple[str, str, int, int], str]] = []
|
|
403
|
+
for index, comment in enumerate(comments):
|
|
404
|
+
author = str(((comment.get("user") or {}).get("login")) or "")
|
|
405
|
+
body = str(comment.get("body") or "")
|
|
406
|
+
if not trusted_comment_author(lane, author, body, comment.get("id"), None):
|
|
407
|
+
continue
|
|
408
|
+
verdict = current_audit_trailer_verdict(lane, body, head_sha=head_sha)
|
|
409
|
+
if verdict:
|
|
410
|
+
try:
|
|
411
|
+
comment_id = int(comment.get("id") or 0)
|
|
412
|
+
except (TypeError, ValueError):
|
|
413
|
+
comment_id = 0
|
|
414
|
+
updated = str(
|
|
415
|
+
comment.get("updated_at")
|
|
416
|
+
or comment.get("updatedAt")
|
|
417
|
+
or comment.get("created_at")
|
|
418
|
+
or comment.get("createdAt")
|
|
419
|
+
or ""
|
|
420
|
+
)
|
|
421
|
+
created = str(comment.get("created_at") or comment.get("createdAt") or "")
|
|
422
|
+
verdicts.append(((updated, created, comment_id, index), verdict))
|
|
423
|
+
if verdicts:
|
|
424
|
+
return max(verdicts, key=lambda item: item[0])[1]
|
|
425
|
+
return ""
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def attested_non_current_audit_heads(
|
|
429
|
+
lanes: Sequence[Mapping[str, Any]],
|
|
430
|
+
comments: Sequence[Mapping[str, Any]],
|
|
431
|
+
*,
|
|
432
|
+
head_sha: str,
|
|
433
|
+
trusted_comment_author: Callable[
|
|
434
|
+
[Mapping[str, Any], str, str, object, str | None],
|
|
435
|
+
bool,
|
|
436
|
+
],
|
|
437
|
+
) -> list[str]:
|
|
438
|
+
heads: list[str] = []
|
|
439
|
+
for comment in comments:
|
|
440
|
+
author = str(((comment.get("user") or {}).get("login")) or "")
|
|
441
|
+
body = str(comment.get("body") or "")
|
|
442
|
+
reviewed = audit_comment_head_sha(body)
|
|
443
|
+
if not reviewed or sha_matches(reviewed, head_sha):
|
|
444
|
+
continue
|
|
445
|
+
for lane in lanes:
|
|
446
|
+
if not terminal_audit_trailer_verdict(lane, body):
|
|
447
|
+
continue
|
|
448
|
+
if not trusted_comment_author(lane, author, body, comment.get("id"), reviewed):
|
|
449
|
+
continue
|
|
450
|
+
heads.append(reviewed)
|
|
451
|
+
break
|
|
452
|
+
return list(dict.fromkeys(heads))
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def audit_run_workflow_path(run: Mapping[str, Any]) -> str:
|
|
456
|
+
return str(run.get("path") or run.get("workflow_path") or run.get("workflowPath") or "")
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def audit_run_matches_pr_head(
|
|
460
|
+
run: Mapping[str, Any],
|
|
461
|
+
*,
|
|
462
|
+
head_sha: str,
|
|
463
|
+
pr_number: int | str,
|
|
464
|
+
) -> bool:
|
|
465
|
+
run_head_sha = str(run.get("head_sha") or "")
|
|
466
|
+
pull_requests = [
|
|
467
|
+
item for item in run.get("pull_requests") or [] if isinstance(item, Mapping)
|
|
468
|
+
]
|
|
469
|
+
for item in pull_requests:
|
|
470
|
+
if not isinstance(item, Mapping):
|
|
471
|
+
continue
|
|
472
|
+
if str(item.get("number") or "") != str(pr_number):
|
|
473
|
+
continue
|
|
474
|
+
pr_head = str(((item.get("head") or {}).get("sha")) or "")
|
|
475
|
+
if pr_head and sha_matches(pr_head, head_sha):
|
|
476
|
+
return True
|
|
477
|
+
if run_head_sha and sha_matches(run_head_sha, head_sha):
|
|
478
|
+
return True
|
|
479
|
+
if pull_requests:
|
|
480
|
+
return False
|
|
481
|
+
if run_head_sha and sha_matches(run_head_sha, head_sha):
|
|
482
|
+
return True
|
|
483
|
+
return False
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _audit_lane_match_tokens(lane: Mapping[str, Any]) -> set[str]:
|
|
487
|
+
tokens = [
|
|
488
|
+
str(lane.get("id") or ""),
|
|
489
|
+
str(lane.get("author_lane") or ""),
|
|
490
|
+
str(lane.get("display_name") or ""),
|
|
491
|
+
]
|
|
492
|
+
for label_key in ("done", "blocked"):
|
|
493
|
+
label = str(lane.get(label_key) or "")
|
|
494
|
+
for suffix in ("-audit-done", "-audit-blocked", "-done", "-blocked"):
|
|
495
|
+
if label.endswith(suffix):
|
|
496
|
+
tokens.append(label[: -len(suffix)])
|
|
497
|
+
return {token.strip().lower().replace("_", "-") for token in tokens if token.strip()}
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def audit_job_matches_lane(job: Mapping[str, Any], lane: Mapping[str, Any]) -> bool:
|
|
501
|
+
name = str(job.get("name") or "").lower().replace("_", "-")
|
|
502
|
+
return any(token in name for token in _audit_lane_match_tokens(lane))
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def audit_run_in_flight_for_lane(
|
|
506
|
+
entry: Mapping[str, Any],
|
|
507
|
+
lane: Mapping[str, Any],
|
|
508
|
+
*,
|
|
509
|
+
head_sha: str,
|
|
510
|
+
pr_number: int | str,
|
|
511
|
+
) -> bool:
|
|
512
|
+
run = entry.get("run") if isinstance(entry.get("run"), Mapping) else entry
|
|
513
|
+
if not isinstance(run, Mapping):
|
|
514
|
+
return False
|
|
515
|
+
if str(run.get("status") or "") not in AUDIT_RUN_NON_TERMINAL_STATUSES:
|
|
516
|
+
return False
|
|
517
|
+
workflows = parse_csv_set(str(lane.get("github_actions_workflows") or ""))
|
|
518
|
+
if not workflow_path_matches(audit_run_workflow_path(run), workflows):
|
|
519
|
+
return False
|
|
520
|
+
if not audit_run_matches_pr_head(run, head_sha=head_sha, pr_number=pr_number):
|
|
521
|
+
return False
|
|
522
|
+
jobs = entry.get("jobs") if isinstance(entry.get("jobs"), list) else []
|
|
523
|
+
non_terminal_jobs = [
|
|
524
|
+
job
|
|
525
|
+
for job in jobs
|
|
526
|
+
if isinstance(job, Mapping)
|
|
527
|
+
and (
|
|
528
|
+
str(job.get("status") or "") in AUDIT_RUN_NON_TERMINAL_STATUSES
|
|
529
|
+
or (str(job.get("status") or "") != "completed" and job.get("conclusion") is None)
|
|
530
|
+
)
|
|
531
|
+
]
|
|
532
|
+
if non_terminal_jobs:
|
|
533
|
+
return any(audit_job_matches_lane(job, lane) for job in non_terminal_jobs)
|
|
534
|
+
return bool(entry.get("jobs_fetch_failed") or not jobs)
|
|
535
|
+
|
|
536
|
+
|
|
313
537
|
def _pr_item_matches_head(
|
|
314
538
|
item: Mapping[str, Any],
|
|
315
539
|
*,
|
|
@@ -357,6 +581,7 @@ def github_actions_comment_attested(
|
|
|
357
581
|
*,
|
|
358
582
|
repo: str,
|
|
359
583
|
body: str,
|
|
584
|
+
comment_id: int | str | None = None,
|
|
360
585
|
issue_number: int,
|
|
361
586
|
head_sha: str,
|
|
362
587
|
workflow_paths: Sequence[str],
|
|
@@ -367,14 +592,19 @@ def github_actions_comment_attested(
|
|
|
367
592
|
"""Return whether a github-actions[bot] audit comment is tied to this PR head."""
|
|
368
593
|
trusted_workflows = tuple(parse_csv_set(",".join(workflow_paths)))
|
|
369
594
|
token_list = tuple(token for token in tokens if token.value)
|
|
370
|
-
|
|
371
|
-
|
|
595
|
+
marker = parse_audit_run_marker(body)
|
|
596
|
+
comment_id_text = str(comment_id or "").strip()
|
|
597
|
+
if not trusted_workflows or not token_list or marker is None or not head_sha:
|
|
598
|
+
return False
|
|
599
|
+
if not comment_id_text or marker.comment_id != comment_id_text:
|
|
600
|
+
return False
|
|
601
|
+
if not audit_run_marker_body_digest_matches(body):
|
|
372
602
|
return False
|
|
373
603
|
try:
|
|
374
604
|
if actions_run_lookup is None:
|
|
375
|
-
run = fetch_actions_run(repo, run_id, tokens=token_list)
|
|
605
|
+
run = fetch_actions_run(repo, marker.run_id, tokens=token_list)
|
|
376
606
|
else:
|
|
377
|
-
run = dict(actions_run_lookup(run_id))
|
|
607
|
+
run = dict(actions_run_lookup(marker.run_id))
|
|
378
608
|
except Exception:
|
|
379
609
|
return False
|
|
380
610
|
|
|
@@ -85,6 +85,7 @@ from .run_status import (
|
|
|
85
85
|
RUN_STATUS_CATEGORY_ALIASES,
|
|
86
86
|
RUN_STATUS_INFRA_ERROR,
|
|
87
87
|
RUN_STATUS_PASS,
|
|
88
|
+
RUN_STATUS_STALE,
|
|
88
89
|
RUN_STATUS_UNKNOWN,
|
|
89
90
|
count_normalized_findings,
|
|
90
91
|
normalize_run_status_category,
|
|
@@ -119,6 +120,7 @@ __all__ = [
|
|
|
119
120
|
"RUN_STATUS_CATEGORY_ALIASES",
|
|
120
121
|
"RUN_STATUS_INFRA_ERROR",
|
|
121
122
|
"RUN_STATUS_PASS",
|
|
123
|
+
"RUN_STATUS_STALE",
|
|
122
124
|
"RUN_STATUS_UNKNOWN",
|
|
123
125
|
"SELECTIVE_USEFUL_RATE",
|
|
124
126
|
"TRUTH_EXPECTATION_ALIASES",
|
|
@@ -12,6 +12,7 @@ from .run_status import (
|
|
|
12
12
|
RUN_STATUS_BLOCKED,
|
|
13
13
|
RUN_STATUS_INFRA_ERROR,
|
|
14
14
|
RUN_STATUS_PASS,
|
|
15
|
+
RUN_STATUS_STALE,
|
|
15
16
|
normalize_run_status_category,
|
|
16
17
|
)
|
|
17
18
|
|
|
@@ -39,6 +40,7 @@ class CellStats:
|
|
|
39
40
|
known_blocked_missed_runs: int = 0
|
|
40
41
|
audit_input_insufficient_runs: int = 0
|
|
41
42
|
infra_error_runs: int = 0
|
|
43
|
+
stale_runs: int = 0
|
|
42
44
|
unknown_blocked_runs: int = 0
|
|
43
45
|
known_clean_runs: int = 0
|
|
44
46
|
known_clean_pass_runs: int = 0
|
|
@@ -94,6 +96,7 @@ class CellStats:
|
|
|
94
96
|
"clean_nonblocking_finding_runs": self.clean_nonblocking_finding_runs,
|
|
95
97
|
"audit_input_insufficient_runs": self.audit_input_insufficient_runs,
|
|
96
98
|
"infra_error_runs": self.infra_error_runs,
|
|
99
|
+
"stale_runs": self.stale_runs,
|
|
97
100
|
"unknown_blocked_runs": self.unknown_blocked_runs,
|
|
98
101
|
"duration_seconds_total": round(self.duration_seconds_total, 3),
|
|
99
102
|
"seconds_per_run": _round_or_none(self.seconds_per_run),
|
|
@@ -209,8 +212,12 @@ def build_effect_report(
|
|
|
209
212
|
cell.audit_input_insufficient_runs += 1
|
|
210
213
|
elif status_category == RUN_STATUS_INFRA_ERROR:
|
|
211
214
|
cell.infra_error_runs += 1
|
|
215
|
+
elif status_category == RUN_STATUS_STALE:
|
|
216
|
+
cell.stale_runs += 1
|
|
212
217
|
else:
|
|
213
218
|
cell.unknown_blocked_runs += 1
|
|
219
|
+
elif status_category == RUN_STATUS_STALE:
|
|
220
|
+
cell.stale_runs += 1
|
|
214
221
|
if known_clean:
|
|
215
222
|
cell.known_clean_runs += 1
|
|
216
223
|
if status_category == RUN_STATUS_PASS and finding_count == 0:
|
|
@@ -489,8 +496,8 @@ def render_effect_report_text(report: Mapping[str, Any]) -> str:
|
|
|
489
496
|
"",
|
|
490
497
|
"## Provider/Lens Cells",
|
|
491
498
|
"",
|
|
492
|
-
"| Provider | Lens | Runs | Blocked caught/missed | Input gaps | Clean pass/false block | Effective catch | Evaluable catch | False-blocker rate | Sec/run |",
|
|
493
|
-
"| --- | --- | ---: | --- | ---: | --- | ---: | ---: | ---: | ---: |",
|
|
499
|
+
"| Provider | Lens | Runs | Blocked caught/missed | Input gaps | Stale | Clean pass/false block | Effective catch | Evaluable catch | False-blocker rate | Sec/run |",
|
|
500
|
+
"| --- | --- | ---: | --- | ---: | ---: | --- | ---: | ---: | ---: | ---: |",
|
|
494
501
|
]
|
|
495
502
|
for cell in report.get("cells", []) or []:
|
|
496
503
|
if not isinstance(cell, Mapping):
|
|
@@ -504,6 +511,7 @@ def render_effect_report_text(report: Mapping[str, Any]) -> str:
|
|
|
504
511
|
str(cell.get("runs", 0)),
|
|
505
512
|
f"{cell.get('known_blocked_caught_runs', 0)}/{cell.get('known_blocked_missed_runs', 0)}",
|
|
506
513
|
str(cell.get("audit_input_insufficient_runs", 0)),
|
|
514
|
+
str(cell.get("stale_runs", 0)),
|
|
507
515
|
f"{cell.get('known_clean_pass_runs', 0)}/{cell.get('blocking_false_positive_runs', 0)}",
|
|
508
516
|
_format_rate(cell.get("effective_catch_rate")),
|
|
509
517
|
_format_rate(cell.get("catch_rate")),
|
|
@@ -12,6 +12,7 @@ from .run_status import (
|
|
|
12
12
|
RUN_STATUS_BLOCKED,
|
|
13
13
|
RUN_STATUS_INFRA_ERROR,
|
|
14
14
|
RUN_STATUS_PASS,
|
|
15
|
+
RUN_STATUS_STALE,
|
|
15
16
|
RUN_STATUS_UNKNOWN,
|
|
16
17
|
normalize_run_status_category,
|
|
17
18
|
)
|
|
@@ -77,6 +78,7 @@ def build_reviewer_evidence_report(corpus: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
77
78
|
profile_known_blocked_missed_run_keys: dict[str, set[tuple[Any, ...]]] = {}
|
|
78
79
|
profile_audit_input_insufficient_run_keys: dict[str, set[tuple[Any, ...]]] = {}
|
|
79
80
|
profile_infra_error_run_keys: dict[str, set[tuple[Any, ...]]] = {}
|
|
81
|
+
profile_stale_run_keys: dict[str, set[tuple[Any, ...]]] = {}
|
|
80
82
|
profile_run_statuses: dict[str, dict[str, int]] = {}
|
|
81
83
|
profile_automated_vs_manual: dict[str, dict[str, int]] = {}
|
|
82
84
|
profile_review_classes: dict[str, set[str]] = {}
|
|
@@ -303,6 +305,8 @@ def build_reviewer_evidence_report(corpus: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
303
305
|
profile_infra_error_run_keys.setdefault(reviewer, set()).add(
|
|
304
306
|
reviewer_run_key
|
|
305
307
|
)
|
|
308
|
+
if status_category == RUN_STATUS_STALE:
|
|
309
|
+
profile_stale_run_keys.setdefault(reviewer, set()).add(reviewer_run_key)
|
|
306
310
|
if status_category == RUN_STATUS_AUDIT_INPUT_INSUFFICIENT:
|
|
307
311
|
profile_audit_input_insufficient_run_keys.setdefault(
|
|
308
312
|
reviewer, set()
|
|
@@ -358,6 +362,7 @@ def build_reviewer_evidence_report(corpus: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
358
362
|
profile_known_blocked_missed_run_keys.get(reviewer, set())
|
|
359
363
|
),
|
|
360
364
|
"infra_error_runs": len(profile_infra_error_run_keys.get(reviewer, set())),
|
|
365
|
+
"stale_runs": len(profile_stale_run_keys.get(reviewer, set())),
|
|
361
366
|
"audit_input_insufficient_runs": len(
|
|
362
367
|
profile_audit_input_insufficient_run_keys.get(reviewer, set())
|
|
363
368
|
),
|
|
@@ -32,6 +32,7 @@ def build_lane_policy_report(metrics: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
32
32
|
false_positive_runs = int(stats.get("blocking_false_positive_runs") or 0)
|
|
33
33
|
known_blocked_missed_runs = int(stats.get("known_blocked_missed_runs") or 0)
|
|
34
34
|
infra_error_runs = int(stats.get("infra_error_runs") or 0)
|
|
35
|
+
stale_runs = int(stats.get("stale_runs") or 0)
|
|
35
36
|
audit_input_insufficient_runs = int(
|
|
36
37
|
stats.get("audit_input_insufficient_runs") or 0
|
|
37
38
|
)
|
|
@@ -158,6 +159,7 @@ def build_lane_policy_report(metrics: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
158
159
|
),
|
|
159
160
|
"known_blocked_missed_runs": known_blocked_missed_runs,
|
|
160
161
|
"infra_error_runs": infra_error_runs,
|
|
162
|
+
"stale_runs": stale_runs,
|
|
161
163
|
"audit_input_insufficient_runs": audit_input_insufficient_runs,
|
|
162
164
|
"observed_pr_count": observed_pr_count,
|
|
163
165
|
"reasons": reasons
|
|
@@ -9,6 +9,7 @@ RUN_STATUS_PASS = "pass"
|
|
|
9
9
|
RUN_STATUS_BLOCKED = "blocked"
|
|
10
10
|
RUN_STATUS_AUDIT_INPUT_INSUFFICIENT = "audit_input_insufficient"
|
|
11
11
|
RUN_STATUS_INFRA_ERROR = "infra_error"
|
|
12
|
+
RUN_STATUS_STALE = "stale"
|
|
12
13
|
RUN_STATUS_UNKNOWN = "unknown"
|
|
13
14
|
|
|
14
15
|
RUN_STATUS_CATEGORY_ALIASES = {
|
|
@@ -36,7 +37,9 @@ RUN_STATUS_CATEGORY_ALIASES = {
|
|
|
36
37
|
"rate_limit": RUN_STATUS_INFRA_ERROR,
|
|
37
38
|
"rate_limited": RUN_STATUS_INFRA_ERROR,
|
|
38
39
|
"setup_error": RUN_STATUS_INFRA_ERROR,
|
|
39
|
-
"stale":
|
|
40
|
+
"stale": RUN_STATUS_STALE,
|
|
41
|
+
"superseded": RUN_STATUS_STALE,
|
|
42
|
+
"superseded_head": RUN_STATUS_STALE,
|
|
40
43
|
"timeout": RUN_STATUS_INFRA_ERROR,
|
|
41
44
|
"timed_out": RUN_STATUS_INFRA_ERROR,
|
|
42
45
|
}
|
|
@@ -56,8 +56,8 @@ def render_value_report_text(report: Mapping[str, Any]) -> str:
|
|
|
56
56
|
f"Run dispositions: {report.get('run_disposition_count', 0)}",
|
|
57
57
|
f"Reviewer runs: {report.get('reviewer_run_count', 0)}",
|
|
58
58
|
"",
|
|
59
|
-
"| Reviewer | Runs | Useful | Negative | Useful rate | Known-clean pass | Known-blocked caught/missed | Infra errors | Input gaps | Auto/manual M/MB/FB | Cost | Sec/run | Cost/useful | Policy | Recommended role |",
|
|
60
|
-
"| --- | ---: | ---: | ---: | ---: | ---: | --- | ---: | ---: | --- | ---: | ---: | ---: | --- | --- |",
|
|
59
|
+
"| Reviewer | Runs | Useful | Negative | Useful rate | Known-clean pass | Known-blocked caught/missed | Infra errors | Stale | Input gaps | Auto/manual M/MB/FB | Cost | Sec/run | Cost/useful | Policy | Recommended role |",
|
|
60
|
+
"| --- | ---: | ---: | ---: | ---: | ---: | --- | ---: | ---: | ---: | --- | ---: | ---: | ---: | --- | --- |",
|
|
61
61
|
]
|
|
62
62
|
if isinstance(profiles, Mapping) and profiles:
|
|
63
63
|
for profile_id, stats in sorted(profiles.items()):
|
|
@@ -89,6 +89,7 @@ def render_value_report_text(report: Mapping[str, Any]) -> str:
|
|
|
89
89
|
str(stats.get("known_clean_pass_runs", 0)),
|
|
90
90
|
caught_missed,
|
|
91
91
|
str(stats.get("infra_error_runs", 0)),
|
|
92
|
+
str(stats.get("stale_runs", 0)),
|
|
92
93
|
str(stats.get("audit_input_insufficient_runs", 0)),
|
|
93
94
|
auto_manual,
|
|
94
95
|
str(stats.get("cost_usd", 0)),
|
|
@@ -109,7 +110,7 @@ def render_value_report_text(report: Mapping[str, Any]) -> str:
|
|
|
109
110
|
+ " |"
|
|
110
111
|
)
|
|
111
112
|
else:
|
|
112
|
-
lines.append("| none | 0 | 0 | 0 | | 0 | 0/0 | 0 | 0 | 0/0/0 | 0 | | | | |")
|
|
113
|
+
lines.append("| none | 0 | 0 | 0 | | 0 | 0/0 | 0 | 0 | 0 | 0/0/0 | 0 | | | | |")
|
|
113
114
|
|
|
114
115
|
recommendations = metrics.get("recommendations", []) if isinstance(metrics, Mapping) else []
|
|
115
116
|
lines.extend(["", "## Recommendations"])
|
|
@@ -188,6 +189,7 @@ def render_value_report_html(report: Mapping[str, Any]) -> str:
|
|
|
188
189
|
f"<td>{esc(stats.get('negative_findings', 0))}</td>"
|
|
189
190
|
f"<td>{esc(stats.get('useful_rate', ''))}</td>"
|
|
190
191
|
f"<td>{esc(caught_missed)}</td>"
|
|
192
|
+
f"<td>{esc(stats.get('stale_runs', 0))}</td>"
|
|
191
193
|
f"<td>{esc(auto_manual)}</td>"
|
|
192
194
|
f"<td>{esc(stats.get('cost_usd', 0))}</td>"
|
|
193
195
|
f"<td>{esc(stats.get('seconds_per_run', ''))}</td>"
|
|
@@ -195,7 +197,7 @@ def render_value_report_html(report: Mapping[str, Any]) -> str:
|
|
|
195
197
|
"</tr>"
|
|
196
198
|
)
|
|
197
199
|
else:
|
|
198
|
-
rows.append("<tr><td colspan='
|
|
200
|
+
rows.append("<tr><td colspan='11'>No reviewer rows yet.</td></tr>")
|
|
199
201
|
|
|
200
202
|
recommendation_items = (
|
|
201
203
|
"\n".join(f"<li>{esc(item)}</li>" for item in recommendations)
|
|
@@ -242,7 +244,7 @@ def render_value_report_html(report: Mapping[str, Any]) -> str:
|
|
|
242
244
|
" </section>\n"
|
|
243
245
|
" <section class='card'>\n"
|
|
244
246
|
" <h2>Reviewer / lens signal</h2>\n"
|
|
245
|
-
" <table><thead><tr><th>Reviewer</th><th>Runs</th><th>Useful</th><th>Negative</th><th>Useful rate</th><th>Blocked caught/missed</th><th>Auto/manual</th><th>Cost</th><th>Sec/run</th><th>Role</th></tr></thead>\n"
|
|
247
|
+
" <table><thead><tr><th>Reviewer</th><th>Runs</th><th>Useful</th><th>Negative</th><th>Useful rate</th><th>Blocked caught/missed</th><th>Stale</th><th>Auto/manual</th><th>Cost</th><th>Sec/run</th><th>Role</th></tr></thead>\n"
|
|
246
248
|
f" <tbody>{''.join(rows)}</tbody></table>\n"
|
|
247
249
|
" </section>\n"
|
|
248
250
|
" <section class='card'>\n"
|