code-mower 0.5.0b41__tar.gz → 0.5.0b42__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.
Files changed (297) hide show
  1. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/PKG-INFO +6 -6
  2. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/README.md +5 -5
  3. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/pyproject.toml +1 -1
  4. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/__init__.py +1 -1
  5. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/audit_labeler_lib.py +129 -1
  6. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/claude_audit_pr.py +27 -2
  7. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/cli.py +1 -1
  8. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/cloud.py +7 -0
  9. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/cloud_client/operations.py +8 -0
  10. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/code_mower_telemetry.py +5 -1
  11. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/codex_audit_pr.py +28 -4
  12. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/init.py +91 -1
  13. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/lane_configs/claude.py +1 -1
  14. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/lane_configs/codex.py +1 -1
  15. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/package_manifest.py +2 -0
  16. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/provider_runners/__init__.py +6 -0
  17. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/provider_runners/pr_worktree.py +36 -1
  18. {code_mower-0.5.0b41 → code_mower-0.5.0b42/src/code_mower}/templates/workflows/builder-provenance.yml.j2 +1 -1
  19. code_mower-0.5.0b42/src/code_mower/templates/workflows/code-mower-gate-health.yml.j2 +321 -0
  20. {code_mower-0.5.0b41 → code_mower-0.5.0b42/src/code_mower}/templates/workflows/code-mower-gate.yml.j2 +36 -4
  21. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/workflows/self-hosted-local-audit.yml.j2 +77 -5
  22. {code_mower-0.5.0b41 → code_mower-0.5.0b42/src/code_mower}/templates/workflows/trailer-comment-labeler.yml.j2 +2 -0
  23. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/trailer_comment_labeler.py +39 -3
  24. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower.egg-info/PKG-INFO +6 -6
  25. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower.egg-info/SOURCES.txt +2 -0
  26. code_mower-0.5.0b42/templates/workflows/code-mower-gate-health.yml.j2 +321 -0
  27. {code_mower-0.5.0b41/src/code_mower → code_mower-0.5.0b42}/templates/workflows/code-mower-gate.yml.j2 +36 -4
  28. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/workflows/self-hosted-local-audit.yml.j2 +77 -5
  29. {code_mower-0.5.0b41/src/code_mower → code_mower-0.5.0b42}/templates/workflows/trailer-comment-labeler.yml.j2 +2 -0
  30. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_audit_comment_posture.py +15 -0
  31. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_provider_runners_pr_worktree.py +58 -0
  32. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_release_hygiene.py +461 -38
  33. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_reviewer_metrics_core.py +47 -0
  34. code_mower-0.5.0b42/tests/test_trailer_comment_labeler.py +151 -0
  35. code_mower-0.5.0b41/tests/test_trailer_comment_labeler.py +0 -66
  36. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/CODE_OF_CONDUCT.md +0 -0
  37. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/LICENSE +0 -0
  38. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/MANIFEST.in +0 -0
  39. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/NOTICE +0 -0
  40. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/SUPPORT.md +0 -0
  41. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/requirements/requirements.txt +0 -0
  42. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/setup.cfg +0 -0
  43. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/adapters/__init__.py +0 -0
  44. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/adapters/_base.py +0 -0
  45. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/adapters/cursor_bugbot.py +0 -0
  46. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/adapters/gitar.py +0 -0
  47. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/adapters/greptile.py +0 -0
  48. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/adapters/qodo.py +0 -0
  49. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/antigravity_cli_audit_pr.py +0 -0
  50. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/audit_handoff_log.py +0 -0
  51. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/audit_progress.py +0 -0
  52. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/blind_review_artifacts.py +0 -0
  53. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/blind_review_coordinator.py +0 -0
  54. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/bootstrap.py +0 -0
  55. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/builder_experiment.py +0 -0
  56. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/builder_runs.py +0 -0
  57. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/__init__.py +0 -0
  58. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/arms.py +0 -0
  59. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/auto_discovery.py +0 -0
  60. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/commands.py +0 -0
  61. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/context_inputs.py +0 -0
  62. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/corpus.py +0 -0
  63. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/effect_report.py +0 -0
  64. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/evidence.py +0 -0
  65. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/evidence_report.py +0 -0
  66. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/identity.py +0 -0
  67. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/metrics.py +0 -0
  68. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/overlap.py +0 -0
  69. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/planning.py +0 -0
  70. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/policy.py +0 -0
  71. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/results.py +0 -0
  72. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/run_results.py +0 -0
  73. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/run_status.py +0 -0
  74. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/runner.py +0 -0
  75. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/truth.py +0 -0
  76. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/calibration/value_report.py +0 -0
  77. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/checks.py +0 -0
  78. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/claude_cli_bounce.py +0 -0
  79. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/claude_cli_environment.py +0 -0
  80. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/clear_stale.py +0 -0
  81. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/cloud_client/__init__.py +0 -0
  82. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/cloud_client/bundle.py +0 -0
  83. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/cloud_client/doctor.py +0 -0
  84. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/cloud_client/dogfood.py +0 -0
  85. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/cloud_client/endpoints.py +0 -0
  86. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/cloud_client/errors.py +0 -0
  87. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/cloud_client/events.py +0 -0
  88. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/cloud_client/export.py +0 -0
  89. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/cloud_client/git_metadata.py +0 -0
  90. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/cloud_client/manifest.py +0 -0
  91. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/cloud_client/reports.py +0 -0
  92. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/cloud_client/setup.py +0 -0
  93. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/cloud_client/upload.py +0 -0
  94. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/code_mower_calibration.py +0 -0
  95. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/code_mower_context_packs.py +0 -0
  96. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/code_mower_merge.py +0 -0
  97. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/coderabbit_cli_audit_pr.py +0 -0
  98. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/codex_audit_env_preflight.py +0 -0
  99. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/codex_audit_schema_smoke.py +0 -0
  100. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/codex_audit_verdict.schema.json +0 -0
  101. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/config.py +0 -0
  102. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor.py +0 -0
  103. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/__init__.py +0 -0
  104. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/cloud.py +0 -0
  105. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/common.py +0 -0
  106. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/github.py +0 -0
  107. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/github_actions.py +0 -0
  108. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/github_actions_cost.py +0 -0
  109. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/github_actions_cost_summary.py +0 -0
  110. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/github_actions_failure_annotations.py +0 -0
  111. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/github_actions_failure_models.py +0 -0
  112. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/github_actions_failure_scan.py +0 -0
  113. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/github_actions_failure_selection.py +0 -0
  114. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/github_actions_failures.py +0 -0
  115. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/github_actions_permissions.py +0 -0
  116. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/github_api.py +0 -0
  117. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/github_branch.py +0 -0
  118. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/github_config.py +0 -0
  119. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/github_provider.py +0 -0
  120. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/github_repo.py +0 -0
  121. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/groups.py +0 -0
  122. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/models.py +0 -0
  123. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/output.py +0 -0
  124. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/presets.py +0 -0
  125. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/privacy.py +0 -0
  126. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/provider_api_model.py +0 -0
  127. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/provider_api_model_openai.py +0 -0
  128. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/provider_api_model_profiles.py +0 -0
  129. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/provider_env.py +0 -0
  130. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/provider_env_required.py +0 -0
  131. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/provider_env_tokens.py +0 -0
  132. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/provider_local_cli.py +0 -0
  133. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/provider_local_cli_commands.py +0 -0
  134. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/provider_local_cli_probe_config.py +0 -0
  135. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/provider_probe.py +0 -0
  136. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/provider_probe_auth.py +0 -0
  137. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/provider_probe_evaluation.py +0 -0
  138. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/provider_probe_json.py +0 -0
  139. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/provider_probe_remediation.py +0 -0
  140. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/provider_review_hygiene.py +0 -0
  141. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/providers.py +0 -0
  142. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/registry.py +0 -0
  143. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/runner.py +0 -0
  144. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/runtime.py +0 -0
  145. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/doctor_checks/runtime_github_auth.py +0 -0
  146. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/gemini_cli_audit_pr.py +0 -0
  147. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/grok_build_audit_pr.py +0 -0
  148. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/hermes_cli_audit_pr.py +0 -0
  149. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/lane_configs/__init__.py +0 -0
  150. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/lane_configs/aider.py +0 -0
  151. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/lane_configs/antigravity_cli.py +0 -0
  152. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/lane_configs/devin.py +0 -0
  153. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/lane_configs/gemini_cli.py +0 -0
  154. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/lane_configs/grok_build.py +0 -0
  155. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/lane_configs/hermes_cli.py +0 -0
  156. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/lane_configs/local_llm.py +0 -0
  157. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/local_llm_audit_pr.py +0 -0
  158. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/local_llm_bakeoff.py +0 -0
  159. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/local_llm_calibration.py +0 -0
  160. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/local_llm_profiles.py +0 -0
  161. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/migration.py +0 -0
  162. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/migration_install.py +0 -0
  163. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/migration_mirror.py +0 -0
  164. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/migration_readiness.py +0 -0
  165. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/migration_rehearsal.py +0 -0
  166. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/next_steps.py +0 -0
  167. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/package.py +0 -0
  168. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/package_content.py +0 -0
  169. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/package_paths.py +0 -0
  170. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/package_rendering.py +0 -0
  171. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/package_static.py +0 -0
  172. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/plan_context.py +0 -0
  173. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/prompts.py +0 -0
  174. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/provider_registry.py +0 -0
  175. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/provider_runners/comments.py +0 -0
  176. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/provider_runners/git.py +0 -0
  177. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/provider_runners/github_auth.py +0 -0
  178. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/provider_runners/github_pr.py +0 -0
  179. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/provider_runners/process.py +0 -0
  180. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/provider_runners/repo_paths.py +0 -0
  181. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/provider_runners/text_schema.py +0 -0
  182. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/provider_runners/verdict_artifacts.py +0 -0
  183. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/provider_runners/workspace.py +0 -0
  184. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/providers/__init__.py +0 -0
  185. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/providers/local_cli.py +0 -0
  186. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/providers/provenance.py +0 -0
  187. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/release_readiness.py +0 -0
  188. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/reviewer_metrics.py +0 -0
  189. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/reviewer_spend.py +0 -0
  190. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/saas_reviewer_labeler.py +0 -0
  191. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/secrets.py +0 -0
  192. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/builder-experiment.example.json +0 -0
  193. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/calibration-corpus.example.json +0 -0
  194. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/calibration-corpus.json +0 -0
  195. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/code-mower.example.yml +0 -0
  196. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/context-packs.example.json +0 -0
  197. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/lane_prompts/base-audit.md +0 -0
  198. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/lane_prompts/calibration-policy.md +0 -0
  199. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/lane_prompts/context-driven-quality.md +0 -0
  200. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/lane_prompts/docs-design.md +0 -0
  201. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/lane_prompts/generic-programming.md +0 -0
  202. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/lane_prompts/operability.md +0 -0
  203. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/lane_prompts/package-runtime.md +0 -0
  204. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/lane_prompts/plan-conformance.md +0 -0
  205. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/lane_prompts/security-threat-model.md +0 -0
  206. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/product-support/code_mower +0 -0
  207. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/product-support/code_mower_standalone_pin.env +0 -0
  208. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/product-support/code_mower_standalone_shadow.sh +0 -0
  209. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/product-support/run_claude_audit_pr.sh +0 -0
  210. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/product-support/run_codex_audit_pr.sh +0 -0
  211. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/product-support/safe_gh_comment.py +0 -0
  212. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/product-support/status_report.py +0 -0
  213. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/providers.yml +0 -0
  214. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/reviewer-spend.example.json +0 -0
  215. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/reviewer-value-report.example.md +0 -0
  216. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/workflows/audit-label-cleanup.yml.j2 +0 -0
  217. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/workflows/hosted-bridge.yml.j2 +0 -0
  218. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/workflows/local-cli-audit.yml.j2 +0 -0
  219. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/workflows/needs-owner-notify.yml.j2 +0 -0
  220. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/workflows/private-standalone-shadow.yml.j2 +0 -0
  221. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/workflows/review-clear-stale.yml.j2 +0 -0
  222. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/workflows/saas-reviewer-labeler.yml.j2 +0 -0
  223. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/templates/workflows/weekly-status.yml.j2 +0 -0
  224. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/versioning.py +0 -0
  225. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower/work_orders.py +0 -0
  226. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower.egg-info/dependency_links.txt +0 -0
  227. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower.egg-info/entry_points.txt +0 -0
  228. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower.egg-info/requires.txt +0 -0
  229. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/src/code_mower.egg-info/top_level.txt +0 -0
  230. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/builder-experiment.example.json +0 -0
  231. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/calibration-corpus.example.json +0 -0
  232. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/calibration-corpus.json +0 -0
  233. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/code-mower.yml.j2 +0 -0
  234. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/context-packs.example.json +0 -0
  235. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/cursor/BUGBOT.md +0 -0
  236. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/providers/acp_bridge.yml +0 -0
  237. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/providers/aider.yml +0 -0
  238. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/providers/antigravity_cli.yml +0 -0
  239. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/providers/claude_audit.yml +0 -0
  240. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/providers/claude_review.yml +0 -0
  241. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/providers/coderabbit_cli.yml +0 -0
  242. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/providers/codex.yml +0 -0
  243. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/providers/cursor_bugbot.yml +0 -0
  244. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/providers/devin.yml +0 -0
  245. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/providers/gemini_cli.yml +0 -0
  246. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/providers/gitar.yml +0 -0
  247. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/providers/greptile.yml +0 -0
  248. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/providers/hermes_cli.yml +0 -0
  249. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/providers/local_llm.yml +0 -0
  250. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/providers/qodo.yml +0 -0
  251. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/providers.yml +0 -0
  252. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/reviewer-spend.example.json +0 -0
  253. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/reviewer-value-report.example.md +0 -0
  254. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/workflows/audit-label-cleanup.yml.j2 +0 -0
  255. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/workflows/blind-review-artifacts-dry-run.yml.j2 +0 -0
  256. {code_mower-0.5.0b41/src/code_mower → code_mower-0.5.0b42}/templates/workflows/builder-provenance.yml.j2 +0 -0
  257. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/workflows/hosted-bridge.yml.j2 +0 -0
  258. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/workflows/local-cli-audit.yml.j2 +0 -0
  259. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/workflows/private-standalone-shadow.yml.j2 +0 -0
  260. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/workflows/review-clear-stale.yml.j2 +0 -0
  261. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/templates/workflows/saas-reviewer-labeler.yml.j2 +0 -0
  262. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_builder_identity.py +0 -0
  263. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_builder_runs.py +0 -0
  264. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_calibration_effect_report.py +0 -0
  265. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_calibration_truth.py +0 -0
  266. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_checks.py +0 -0
  267. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_claude_cli_bounce.py +0 -0
  268. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_clear_stale.py +0 -0
  269. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_cloud_client.py +0 -0
  270. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_cloud_git_metadata.py +0 -0
  271. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_cloud_manifest.py +0 -0
  272. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_cloud_reports.py +0 -0
  273. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_coderabbit_cli_audit_pr.py +0 -0
  274. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_codex_audit_env_preflight.py +0 -0
  275. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_doctor_actions_cost_summary.py +0 -0
  276. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_doctor_actions_failure_selection.py +0 -0
  277. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_doctor_boundaries.py +0 -0
  278. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_doctor_github_checks.py +0 -0
  279. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_doctor_output.py +0 -0
  280. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_doctor_privacy.py +0 -0
  281. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_doctor_provider_env_required.py +0 -0
  282. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_doctor_provider_env_tokens.py +0 -0
  283. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_doctor_provider_probe.py +0 -0
  284. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_doctor_provider_review_hygiene.py +0 -0
  285. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_doctor_registry.py +0 -0
  286. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_local_llm_github_helpers.py +0 -0
  287. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_plan_context.py +0 -0
  288. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_provider_local_cli.py +0 -0
  289. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_provider_runners_comments.py +0 -0
  290. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_provider_runners_git.py +0 -0
  291. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_provider_runners_github_pr.py +0 -0
  292. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_provider_runners_process.py +0 -0
  293. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_provider_runners_repo_paths.py +0 -0
  294. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_provider_runners_text_schema.py +0 -0
  295. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_provider_runners_workspace.py +0 -0
  296. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/tests/test_reviewer_spend.py +0 -0
  297. {code_mower-0.5.0b41 → code_mower-0.5.0b42}/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.0b41
3
+ Version: 0.5.0b42
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.0b41
106
+ pipx install --python python3.12 code-mower==0.5.0b42
107
107
  code-mower --version
108
108
  ```
109
109
 
110
- `0.5.0b41` is a beta release. Until Code Mower publishes a stable `1.0`
110
+ `0.5.0b42` 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.0b41`.
158
+ the latest 10/10 public-package readiness proof for `code-mower==0.5.0b42`.
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.41` from
313
+ The current public beta is `v0.5.0-beta.42` from
314
314
  [codemower-ai/code-mower](https://github.com/codemower-ai/code-mower), published
315
- as `code-mower==0.5.0b41` on [PyPI](https://pypi.org/project/code-mower/).
315
+ as `code-mower==0.5.0b42` 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.0b41
91
+ pipx install --python python3.12 code-mower==0.5.0b42
92
92
  code-mower --version
93
93
  ```
94
94
 
95
- `0.5.0b41` is a beta release. Until Code Mower publishes a stable `1.0`
95
+ `0.5.0b42` 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.0b41`.
143
+ the latest 10/10 public-package readiness proof for `code-mower==0.5.0b42`.
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.41` from
298
+ The current public beta is `v0.5.0-beta.42` from
299
299
  [codemower-ai/code-mower](https://github.com/codemower-ai/code-mower), published
300
- as `code-mower==0.5.0b41` on [PyPI](https://pypi.org/project/code-mower/).
300
+ as `code-mower==0.5.0b42` 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
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code-mower"
7
- version = "0.5.0b41"
7
+ version = "0.5.0b42"
8
8
  description = "Multi-reviewer AI code audit orchestration"
9
9
  requires-python = ">=3.11"
10
10
  readme = "README.md"
@@ -1,3 +1,3 @@
1
1
  """Code Mower package."""
2
2
 
3
- __version__ = "0.5.0b41"
3
+ __version__ = "0.5.0b42"
@@ -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,
@@ -113,6 +116,7 @@ MAX_FINDING_DETAIL_CHARS = 4_000
113
116
  STALE_TRAILER = "<!-- CLAUDE_AUDIT_STATE: needs-claude-audit -->"
114
117
  DONE_TRAILER = "<!-- CLAUDE_AUDIT_STATE: claude-audit-done -->"
115
118
  BLOCKED_TRAILER = "<!-- CLAUDE_AUDIT_STATE: claude-audit-blocked -->"
119
+ AUDIT_RUN_TRAILER_PREFIX = "<!-- CODE_MOWER_AUDIT_RUN:"
116
120
 
117
121
 
118
122
  CLAUDE_VERDICT_SCHEMA: Dict[str, Any] = {
@@ -387,7 +391,17 @@ def _fetch_base_sha_for_diff(local_repo: Path, base_ref: str) -> str:
387
391
  return _shared_fetch_base_ref_sha(local_repo, base_ref)
388
392
 
389
393
 
390
- def _fetch_pr_head_sha_for_diff(local_repo: Path, pr_number: int) -> str:
394
+ def _fetch_pr_head_sha_for_diff(
395
+ local_repo: Path,
396
+ pr_number: int,
397
+ expected_head_sha: str | None = None,
398
+ ) -> str:
399
+ if expected_head_sha:
400
+ return _shared_fetch_pr_head_sha_unless_local_matches(
401
+ local_repo,
402
+ pr_number,
403
+ expected_head_sha=expected_head_sha,
404
+ )
391
405
  return _shared_fetch_pr_head_sha(local_repo, pr_number)
392
406
 
393
407
 
@@ -482,7 +496,11 @@ def _build_diff_context(
482
496
  )
483
497
 
484
498
  fetched_base_ref = _fetch_base_sha_for_diff(local_repo, base_ref)
485
- fetched_head_ref = _fetch_pr_head_sha_for_diff(local_repo, pr_number)
499
+ fetched_head_ref = _fetch_pr_head_sha_for_diff(
500
+ local_repo,
501
+ pr_number,
502
+ expected_head_sha=expected_head_sha,
503
+ )
486
504
  if fetched_head_ref.lower() != expected_head_sha.lower():
487
505
  raise FetchedHeadMismatch(expected_head_sha, fetched_head_ref)
488
506
  diff_range = f"{fetched_base_ref}...{fetched_head_ref}"
@@ -705,10 +723,13 @@ def format_comment(
705
723
  stale_end_sha: Optional[str] = None,
706
724
  is_unknown: bool = False,
707
725
  merge_authority: bool = True,
726
+ actions_run_id: Optional[str] = None,
708
727
  ) -> str:
709
728
  posture = "merge-authority lane" if merge_authority else "informational only"
710
729
  header = f"## Claude audit ({posture})\n\n"
711
730
  header += f"Head SHA: `{head_sha}`\n"
731
+ if actions_run_id:
732
+ header += f"{AUDIT_RUN_TRAILER_PREFIX} run_id={actions_run_id} -->\n"
712
733
  if is_stale:
713
734
  body = (
714
735
  header
@@ -916,6 +937,7 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
916
937
  pr_meta_after = fetch_pull_request(repo, pr_number, token=config.github_token)
917
938
  head_sha_end = pr_meta_after["head"]["sha"]
918
939
  is_stale = head_sha_start != head_sha_end
940
+ actions_run_id = os.environ.get("GITHUB_RUN_ID") or None
919
941
 
920
942
  if is_stale:
921
943
  comment_body = format_comment(
@@ -924,6 +946,7 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
924
946
  is_stale=True,
925
947
  stale_end_sha=head_sha_end,
926
948
  merge_authority=config.merge_authority,
949
+ actions_run_id=actions_run_id,
927
950
  )
928
951
  result_verdict = "STALE"
929
952
  trailer = STALE_TRAILER
@@ -933,6 +956,7 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
933
956
  head_sha_start,
934
957
  is_unknown=True,
935
958
  merge_authority=config.merge_authority,
959
+ actions_run_id=actions_run_id,
936
960
  )
937
961
  result_verdict = "UNKNOWN"
938
962
  trailer = STALE_TRAILER
@@ -941,6 +965,7 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
941
965
  parsed,
942
966
  head_sha_start,
943
967
  merge_authority=config.merge_authority,
968
+ actions_run_id=actions_run_id,
944
969
  )
945
970
  result_verdict = parsed.verdict
946
971
  trailer = BLOCKED_TRAILER if parsed.verdict == "BLOCKED" else DONE_TRAILER
@@ -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.0b41`, or run source checkouts with "
17
+ "`pipx install code-mower==0.5.0b42`, 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,
@@ -315,6 +315,7 @@ def reviewer_runs_upload(
315
315
  team_id: str,
316
316
  install_id: str,
317
317
  limit: int,
318
+ offset: int = 0,
318
319
  endpoint: str,
319
320
  token_env: str,
320
321
  yes: bool,
@@ -323,6 +324,8 @@ def reviewer_runs_upload(
323
324
  ) -> dict[str, Any]:
324
325
  if limit < 1 or limit > MAX_EVENT_COUNT:
325
326
  raise CloudBundleError(f"--limit must be between 1 and {MAX_EVENT_COUNT}")
327
+ if offset < 0:
328
+ raise CloudBundleError("--offset must be non-negative")
326
329
  repo_path = repo_path.expanduser().resolve()
327
330
  detected_repo_slug = repo_slug or detect_repo_slug(repo_path)
328
331
  if not detected_repo_slug:
@@ -336,6 +339,7 @@ def reviewer_runs_upload(
336
339
  verdicts,
337
340
  repo=detected_repo_slug,
338
341
  limit=limit,
342
+ offset=offset,
339
343
  include_git_ref=include_git_ref,
340
344
  )
341
345
  except ValueError as exc:
@@ -348,6 +352,7 @@ def reviewer_runs_upload(
348
352
  "event_count": 0,
349
353
  "verdicts": str(verdicts.expanduser()),
350
354
  "git_ref_included": include_git_ref,
355
+ "offset": offset,
351
356
  }
352
357
  export_result = build_cloud_bundle(
353
358
  reports=[],
@@ -370,6 +375,7 @@ def reviewer_runs_upload(
370
375
  "status": "doctor_failed",
371
376
  "repo_slug": detected_repo_slug,
372
377
  "event_count": len(events),
378
+ "offset": offset,
373
379
  "export": export_result,
374
380
  "doctor": doctor_result,
375
381
  }
@@ -380,6 +386,7 @@ def reviewer_runs_upload(
380
386
  "status": "dry_run",
381
387
  "repo_slug": detected_repo_slug,
382
388
  "event_count": len(events),
389
+ "offset": offset,
383
390
  "export": export_result,
384
391
  "doctor": doctor_result,
385
392
  "upload": build_dogfood_dry_run_preview(endpoint=endpoint, payload=payload),
@@ -394,6 +401,7 @@ def reviewer_runs_upload(
394
401
  "status": "uploaded",
395
402
  "repo_slug": detected_repo_slug,
396
403
  "event_count": len(events),
404
+ "offset": offset,
397
405
  "export": export_result,
398
406
  "doctor": doctor_result,
399
407
  "upload": post_upload_payload(
@@ -233,8 +233,11 @@ def export_reviewer_run_events_from_verdicts(
233
233
  *,
234
234
  repo: str = "",
235
235
  limit: int = 0,
236
+ offset: int = 0,
236
237
  include_git_ref: bool = False,
237
238
  ) -> list[dict[str, Any]]:
239
+ if offset < 0:
240
+ raise ValueError("offset must be non-negative")
238
241
  repo_filter = _normalize_repo_slug(repo) if repo else ""
239
242
  events: list[dict[str, Any]] = []
240
243
  for path in _iter_verdict_artifact_paths(root.expanduser()):
@@ -262,7 +265,8 @@ def export_reviewer_run_events_from_verdicts(
262
265
  key=lambda event: (str(event.get("created_at") or ""), str(event.get("event_id") or "")),
263
266
  reverse=True,
264
267
  )
265
- return events[:limit] if limit else events
268
+ selected = events[offset:] if offset else events
269
+ return selected[:limit] if limit else selected
266
270
 
267
271
 
268
272
  def load_jsonl_events(path: Path) -> list[dict[str, Any]]:
@@ -17,8 +17,10 @@ Pipeline per audit:
17
17
  `CODEX_AUDIT_REPO_PATHS` (the CLI requires a real git checkout for
18
18
  the built-in review).
19
19
  2. Fetch the PR head SHA via the GitHub API.
20
- 3. `git fetch origin pull/<N>/head` + create a temporary worktree at
21
- the head SHA (detached, so we don't pollute branch namespace).
20
+ 3. Ensure the PR head commit is locally available: skip the PR-head fetch
21
+ when the configured checkout already matches the GitHub API head SHA,
22
+ otherwise `git fetch origin pull/<N>/head`; then create a temporary
23
+ worktree at the head SHA (detached, so we don't pollute branch namespace).
22
24
  4. Run `codex exec --ignore-user-config --sandbox read-only review
23
25
  --base origin/main` from that worktree, capturing the final review
24
26
  prose via `--output-last-message`.
@@ -91,6 +93,7 @@ if __package__ in {None, "", "tools"}:
91
93
  fetch_pull_request,
92
94
  fetch_base_ref as _shared_fetch_base_ref,
93
95
  fetch_pr_head as _shared_fetch_pr_head,
96
+ fetch_pr_head_unless_local_matches as _shared_fetch_pr_head_unless_local_matches,
94
97
  limit_comment_body,
95
98
  one_line as _one_line,
96
99
  parse_repo_paths as _parse_repo_paths,
@@ -113,6 +116,7 @@ if __package__ in {None, "", "tools"}:
113
116
  fetch_pull_request,
114
117
  fetch_base_ref as _shared_fetch_base_ref,
115
118
  fetch_pr_head as _shared_fetch_pr_head,
119
+ fetch_pr_head_unless_local_matches as _shared_fetch_pr_head_unless_local_matches,
116
120
  limit_comment_body,
117
121
  one_line as _one_line,
118
122
  parse_repo_paths as _parse_repo_paths,
@@ -136,6 +140,7 @@ else: # pragma: no cover - exercised after package extraction.
136
140
  fetch_base_ref as _shared_fetch_base_ref,
137
141
  fetch_pull_request,
138
142
  fetch_pr_head as _shared_fetch_pr_head,
143
+ fetch_pr_head_unless_local_matches as _shared_fetch_pr_head_unless_local_matches,
139
144
  limit_comment_body,
140
145
  one_line as _one_line,
141
146
  parse_repo_paths as _parse_repo_paths,
@@ -305,6 +310,7 @@ class ReviewContextDiagnostics:
305
310
  STALE_TRAILER = "<!-- CODEX_AUDIT_STATE: needs-codex-audit -->"
306
311
  DONE_TRAILER = "<!-- CODEX_AUDIT_STATE: codex-audit-done -->"
307
312
  BLOCKED_TRAILER = "<!-- CODEX_AUDIT_STATE: codex-audit-blocked -->"
313
+ AUDIT_RUN_TRAILER_PREFIX = "<!-- CODE_MOWER_AUDIT_RUN:"
308
314
 
309
315
 
310
316
  # ----- Structured verdict validation -----
@@ -654,8 +660,19 @@ def _remove_worktree(local_repo: Path, worktree_path: Path) -> None:
654
660
  _shared_remove_worktree(local_repo, worktree_path)
655
661
 
656
662
 
657
- def _fetch_pr_head(local_repo: Path, pr_number: int) -> None:
663
+ def _fetch_pr_head(
664
+ local_repo: Path,
665
+ pr_number: int,
666
+ expected_head_sha: str | None = None,
667
+ ) -> None:
658
668
  """Ensure the PR's head commit is locally available."""
669
+ if expected_head_sha:
670
+ _shared_fetch_pr_head_unless_local_matches(
671
+ local_repo,
672
+ pr_number,
673
+ expected_head_sha=expected_head_sha,
674
+ )
675
+ return
659
676
  _shared_fetch_pr_head(local_repo, pr_number)
660
677
 
661
678
 
@@ -1172,11 +1189,14 @@ def format_comment(
1172
1189
  stale_end_sha: Optional[str] = None,
1173
1190
  is_unknown: bool = False,
1174
1191
  merge_authority: bool = True,
1192
+ actions_run_id: Optional[str] = None,
1175
1193
  ) -> str:
1176
1194
  """Build the GitHub comment body with header, prose, and trailer."""
1177
1195
  posture = "merge-authority lane" if merge_authority else "informational only"
1178
1196
  header = f"## Codex audit ({posture})\n\n"
1179
1197
  header += f"Head SHA: `{head_sha}`\n"
1198
+ if actions_run_id:
1199
+ header += f"{AUDIT_RUN_TRAILER_PREFIX} run_id={actions_run_id} -->\n"
1180
1200
  if is_stale:
1181
1201
  body = (
1182
1202
  header
@@ -1293,7 +1313,7 @@ def audit_pr(config: AuditConfig, repo: str, pr_number: int) -> AuditResult:
1293
1313
 
1294
1314
  # Ensure both the PR head AND the base ref are locally fetched
1295
1315
  # before running the review. Stale base = wrong diff = wrong review.
1296
- _fetch_pr_head(local_repo, pr_number)
1316
+ _fetch_pr_head(local_repo, pr_number, head_sha_start)
1297
1317
  _fetch_base_ref(local_repo, config.base_ref)
1298
1318
  review_context_summary = "review context diagnostics unavailable"
1299
1319
  try:
@@ -1455,6 +1475,7 @@ def audit_pr(config: AuditConfig, repo: str, pr_number: int) -> AuditResult:
1455
1475
  pr_meta_after = fetch_pull_request(repo, pr_number, token=config.github_token)
1456
1476
  head_sha_end = pr_meta_after["head"]["sha"]
1457
1477
  is_stale = head_sha_start != head_sha_end
1478
+ actions_run_id = os.environ.get("GITHUB_RUN_ID") or None
1458
1479
 
1459
1480
  if is_stale:
1460
1481
  comment_body = format_comment(
@@ -1463,6 +1484,7 @@ def audit_pr(config: AuditConfig, repo: str, pr_number: int) -> AuditResult:
1463
1484
  is_stale=True,
1464
1485
  stale_end_sha=head_sha_end,
1465
1486
  merge_authority=config.merge_authority,
1487
+ actions_run_id=actions_run_id,
1466
1488
  )
1467
1489
  result_verdict = "STALE"
1468
1490
  trailer = STALE_TRAILER
@@ -1497,6 +1519,7 @@ def audit_pr(config: AuditConfig, repo: str, pr_number: int) -> AuditResult:
1497
1519
  head_sha_start,
1498
1520
  is_unknown=True,
1499
1521
  merge_authority=config.merge_authority,
1522
+ actions_run_id=actions_run_id,
1500
1523
  )
1501
1524
  result_verdict = "UNKNOWN"
1502
1525
  trailer = STALE_TRAILER
@@ -1505,6 +1528,7 @@ def audit_pr(config: AuditConfig, repo: str, pr_number: int) -> AuditResult:
1505
1528
  parsed,
1506
1529
  head_sha_start,
1507
1530
  merge_authority=config.merge_authority,
1531
+ actions_run_id=actions_run_id,
1508
1532
  )
1509
1533
  result_verdict = parsed.verdict
1510
1534
  trailer = BLOCKED_TRAILER if parsed.verdict == "BLOCKED" else DONE_TRAILER