code-mower 0.5.0b42__tar.gz → 0.5.0b43__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/PKG-INFO +6 -6
  2. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/README.md +5 -5
  3. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/pyproject.toml +1 -1
  4. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/__init__.py +1 -1
  5. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/claude_audit_pr.py +195 -2
  6. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/cli.py +1 -1
  7. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/builder-provenance.yml.j2 +1 -1
  8. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower.egg-info/PKG-INFO +6 -6
  9. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower.egg-info/SOURCES.txt +1 -0
  10. code_mower-0.5.0b43/tests/test_claude_audit_pr.py +175 -0
  11. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_release_hygiene.py +34 -34
  12. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/CODE_OF_CONDUCT.md +0 -0
  13. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/LICENSE +0 -0
  14. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/MANIFEST.in +0 -0
  15. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/NOTICE +0 -0
  16. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/SUPPORT.md +0 -0
  17. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/requirements/requirements.txt +0 -0
  18. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/setup.cfg +0 -0
  19. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/adapters/__init__.py +0 -0
  20. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/adapters/_base.py +0 -0
  21. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/adapters/cursor_bugbot.py +0 -0
  22. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/adapters/gitar.py +0 -0
  23. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/adapters/greptile.py +0 -0
  24. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/adapters/qodo.py +0 -0
  25. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/antigravity_cli_audit_pr.py +0 -0
  26. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/audit_handoff_log.py +0 -0
  27. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/audit_labeler_lib.py +0 -0
  28. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/audit_progress.py +0 -0
  29. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/blind_review_artifacts.py +0 -0
  30. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/blind_review_coordinator.py +0 -0
  31. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/bootstrap.py +0 -0
  32. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/builder_experiment.py +0 -0
  33. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/builder_runs.py +0 -0
  34. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/__init__.py +0 -0
  35. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/arms.py +0 -0
  36. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/auto_discovery.py +0 -0
  37. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/commands.py +0 -0
  38. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/context_inputs.py +0 -0
  39. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/corpus.py +0 -0
  40. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/effect_report.py +0 -0
  41. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/evidence.py +0 -0
  42. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/evidence_report.py +0 -0
  43. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/identity.py +0 -0
  44. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/metrics.py +0 -0
  45. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/overlap.py +0 -0
  46. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/planning.py +0 -0
  47. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/policy.py +0 -0
  48. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/results.py +0 -0
  49. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/run_results.py +0 -0
  50. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/run_status.py +0 -0
  51. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/runner.py +0 -0
  52. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/truth.py +0 -0
  53. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/calibration/value_report.py +0 -0
  54. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/checks.py +0 -0
  55. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/claude_cli_bounce.py +0 -0
  56. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/claude_cli_environment.py +0 -0
  57. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/clear_stale.py +0 -0
  58. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/cloud.py +0 -0
  59. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/cloud_client/__init__.py +0 -0
  60. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/cloud_client/bundle.py +0 -0
  61. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/cloud_client/doctor.py +0 -0
  62. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/cloud_client/dogfood.py +0 -0
  63. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/cloud_client/endpoints.py +0 -0
  64. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/cloud_client/errors.py +0 -0
  65. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/cloud_client/events.py +0 -0
  66. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/cloud_client/export.py +0 -0
  67. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/cloud_client/git_metadata.py +0 -0
  68. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/cloud_client/manifest.py +0 -0
  69. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/cloud_client/operations.py +0 -0
  70. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/cloud_client/reports.py +0 -0
  71. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/cloud_client/setup.py +0 -0
  72. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/cloud_client/upload.py +0 -0
  73. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/code_mower_calibration.py +0 -0
  74. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/code_mower_context_packs.py +0 -0
  75. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/code_mower_merge.py +0 -0
  76. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/code_mower_telemetry.py +0 -0
  77. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/coderabbit_cli_audit_pr.py +0 -0
  78. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/codex_audit_env_preflight.py +0 -0
  79. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/codex_audit_pr.py +0 -0
  80. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/codex_audit_schema_smoke.py +0 -0
  81. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/codex_audit_verdict.schema.json +0 -0
  82. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/config.py +0 -0
  83. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor.py +0 -0
  84. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/__init__.py +0 -0
  85. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/cloud.py +0 -0
  86. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/common.py +0 -0
  87. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github.py +0 -0
  88. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_actions.py +0 -0
  89. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_actions_cost.py +0 -0
  90. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_actions_cost_summary.py +0 -0
  91. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_actions_failure_annotations.py +0 -0
  92. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_actions_failure_models.py +0 -0
  93. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_actions_failure_scan.py +0 -0
  94. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_actions_failure_selection.py +0 -0
  95. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_actions_failures.py +0 -0
  96. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_actions_permissions.py +0 -0
  97. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_api.py +0 -0
  98. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_branch.py +0 -0
  99. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_config.py +0 -0
  100. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_provider.py +0 -0
  101. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/github_repo.py +0 -0
  102. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/groups.py +0 -0
  103. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/models.py +0 -0
  104. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/output.py +0 -0
  105. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/presets.py +0 -0
  106. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/privacy.py +0 -0
  107. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_api_model.py +0 -0
  108. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_api_model_openai.py +0 -0
  109. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_api_model_profiles.py +0 -0
  110. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_env.py +0 -0
  111. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_env_required.py +0 -0
  112. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_env_tokens.py +0 -0
  113. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_local_cli.py +0 -0
  114. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_local_cli_commands.py +0 -0
  115. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_local_cli_probe_config.py +0 -0
  116. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_probe.py +0 -0
  117. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_probe_auth.py +0 -0
  118. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_probe_evaluation.py +0 -0
  119. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_probe_json.py +0 -0
  120. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_probe_remediation.py +0 -0
  121. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/provider_review_hygiene.py +0 -0
  122. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/providers.py +0 -0
  123. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/registry.py +0 -0
  124. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/runner.py +0 -0
  125. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/runtime.py +0 -0
  126. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/doctor_checks/runtime_github_auth.py +0 -0
  127. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/gemini_cli_audit_pr.py +0 -0
  128. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/grok_build_audit_pr.py +0 -0
  129. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/hermes_cli_audit_pr.py +0 -0
  130. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/init.py +0 -0
  131. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/lane_configs/__init__.py +0 -0
  132. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/lane_configs/aider.py +0 -0
  133. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/lane_configs/antigravity_cli.py +0 -0
  134. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/lane_configs/claude.py +0 -0
  135. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/lane_configs/codex.py +0 -0
  136. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/lane_configs/devin.py +0 -0
  137. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/lane_configs/gemini_cli.py +0 -0
  138. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/lane_configs/grok_build.py +0 -0
  139. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/lane_configs/hermes_cli.py +0 -0
  140. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/lane_configs/local_llm.py +0 -0
  141. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/local_llm_audit_pr.py +0 -0
  142. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/local_llm_bakeoff.py +0 -0
  143. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/local_llm_calibration.py +0 -0
  144. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/local_llm_profiles.py +0 -0
  145. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/migration.py +0 -0
  146. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/migration_install.py +0 -0
  147. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/migration_mirror.py +0 -0
  148. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/migration_readiness.py +0 -0
  149. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/migration_rehearsal.py +0 -0
  150. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/next_steps.py +0 -0
  151. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/package.py +0 -0
  152. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/package_content.py +0 -0
  153. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/package_manifest.py +0 -0
  154. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/package_paths.py +0 -0
  155. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/package_rendering.py +0 -0
  156. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/package_static.py +0 -0
  157. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/plan_context.py +0 -0
  158. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/prompts.py +0 -0
  159. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/provider_registry.py +0 -0
  160. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/provider_runners/__init__.py +0 -0
  161. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/provider_runners/comments.py +0 -0
  162. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/provider_runners/git.py +0 -0
  163. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/provider_runners/github_auth.py +0 -0
  164. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/provider_runners/github_pr.py +0 -0
  165. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/provider_runners/pr_worktree.py +0 -0
  166. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/provider_runners/process.py +0 -0
  167. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/provider_runners/repo_paths.py +0 -0
  168. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/provider_runners/text_schema.py +0 -0
  169. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/provider_runners/verdict_artifacts.py +0 -0
  170. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/provider_runners/workspace.py +0 -0
  171. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/providers/__init__.py +0 -0
  172. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/providers/local_cli.py +0 -0
  173. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/providers/provenance.py +0 -0
  174. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/release_readiness.py +0 -0
  175. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/reviewer_metrics.py +0 -0
  176. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/reviewer_spend.py +0 -0
  177. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/saas_reviewer_labeler.py +0 -0
  178. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/secrets.py +0 -0
  179. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/builder-experiment.example.json +0 -0
  180. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/calibration-corpus.example.json +0 -0
  181. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/calibration-corpus.json +0 -0
  182. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/code-mower.example.yml +0 -0
  183. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/context-packs.example.json +0 -0
  184. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/lane_prompts/base-audit.md +0 -0
  185. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/lane_prompts/calibration-policy.md +0 -0
  186. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/lane_prompts/context-driven-quality.md +0 -0
  187. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/lane_prompts/docs-design.md +0 -0
  188. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/lane_prompts/generic-programming.md +0 -0
  189. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/lane_prompts/operability.md +0 -0
  190. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/lane_prompts/package-runtime.md +0 -0
  191. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/lane_prompts/plan-conformance.md +0 -0
  192. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/lane_prompts/security-threat-model.md +0 -0
  193. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/product-support/code_mower +0 -0
  194. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/product-support/code_mower_standalone_pin.env +0 -0
  195. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/product-support/code_mower_standalone_shadow.sh +0 -0
  196. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/product-support/run_claude_audit_pr.sh +0 -0
  197. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/product-support/run_codex_audit_pr.sh +0 -0
  198. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/product-support/safe_gh_comment.py +0 -0
  199. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/product-support/status_report.py +0 -0
  200. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/providers.yml +0 -0
  201. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/reviewer-spend.example.json +0 -0
  202. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/reviewer-value-report.example.md +0 -0
  203. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/audit-label-cleanup.yml.j2 +0 -0
  204. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/code-mower-gate-health.yml.j2 +0 -0
  205. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/code-mower-gate.yml.j2 +0 -0
  206. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/hosted-bridge.yml.j2 +0 -0
  207. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/local-cli-audit.yml.j2 +0 -0
  208. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/needs-owner-notify.yml.j2 +0 -0
  209. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/private-standalone-shadow.yml.j2 +0 -0
  210. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/review-clear-stale.yml.j2 +0 -0
  211. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/saas-reviewer-labeler.yml.j2 +0 -0
  212. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/self-hosted-local-audit.yml.j2 +0 -0
  213. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/trailer-comment-labeler.yml.j2 +0 -0
  214. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/templates/workflows/weekly-status.yml.j2 +0 -0
  215. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/trailer_comment_labeler.py +0 -0
  216. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/versioning.py +0 -0
  217. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower/work_orders.py +0 -0
  218. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower.egg-info/dependency_links.txt +0 -0
  219. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower.egg-info/entry_points.txt +0 -0
  220. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower.egg-info/requires.txt +0 -0
  221. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/src/code_mower.egg-info/top_level.txt +0 -0
  222. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/builder-experiment.example.json +0 -0
  223. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/calibration-corpus.example.json +0 -0
  224. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/calibration-corpus.json +0 -0
  225. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/code-mower.yml.j2 +0 -0
  226. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/context-packs.example.json +0 -0
  227. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/cursor/BUGBOT.md +0 -0
  228. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/providers/acp_bridge.yml +0 -0
  229. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/providers/aider.yml +0 -0
  230. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/providers/antigravity_cli.yml +0 -0
  231. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/providers/claude_audit.yml +0 -0
  232. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/providers/claude_review.yml +0 -0
  233. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/providers/coderabbit_cli.yml +0 -0
  234. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/providers/codex.yml +0 -0
  235. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/providers/cursor_bugbot.yml +0 -0
  236. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/providers/devin.yml +0 -0
  237. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/providers/gemini_cli.yml +0 -0
  238. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/providers/gitar.yml +0 -0
  239. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/providers/greptile.yml +0 -0
  240. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/providers/hermes_cli.yml +0 -0
  241. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/providers/local_llm.yml +0 -0
  242. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/providers/qodo.yml +0 -0
  243. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/providers.yml +0 -0
  244. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/reviewer-spend.example.json +0 -0
  245. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/reviewer-value-report.example.md +0 -0
  246. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/workflows/audit-label-cleanup.yml.j2 +0 -0
  247. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/workflows/blind-review-artifacts-dry-run.yml.j2 +0 -0
  248. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/workflows/builder-provenance.yml.j2 +0 -0
  249. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/workflows/code-mower-gate-health.yml.j2 +0 -0
  250. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/workflows/code-mower-gate.yml.j2 +0 -0
  251. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/workflows/hosted-bridge.yml.j2 +0 -0
  252. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/workflows/local-cli-audit.yml.j2 +0 -0
  253. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/workflows/private-standalone-shadow.yml.j2 +0 -0
  254. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/workflows/review-clear-stale.yml.j2 +0 -0
  255. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/workflows/saas-reviewer-labeler.yml.j2 +0 -0
  256. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/workflows/self-hosted-local-audit.yml.j2 +0 -0
  257. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/templates/workflows/trailer-comment-labeler.yml.j2 +0 -0
  258. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_audit_comment_posture.py +0 -0
  259. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_builder_identity.py +0 -0
  260. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_builder_runs.py +0 -0
  261. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_calibration_effect_report.py +0 -0
  262. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_calibration_truth.py +0 -0
  263. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_checks.py +0 -0
  264. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_claude_cli_bounce.py +0 -0
  265. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_clear_stale.py +0 -0
  266. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_cloud_client.py +0 -0
  267. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_cloud_git_metadata.py +0 -0
  268. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_cloud_manifest.py +0 -0
  269. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_cloud_reports.py +0 -0
  270. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_coderabbit_cli_audit_pr.py +0 -0
  271. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_codex_audit_env_preflight.py +0 -0
  272. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_doctor_actions_cost_summary.py +0 -0
  273. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_doctor_actions_failure_selection.py +0 -0
  274. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_doctor_boundaries.py +0 -0
  275. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_doctor_github_checks.py +0 -0
  276. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_doctor_output.py +0 -0
  277. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_doctor_privacy.py +0 -0
  278. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_doctor_provider_env_required.py +0 -0
  279. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_doctor_provider_env_tokens.py +0 -0
  280. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_doctor_provider_probe.py +0 -0
  281. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_doctor_provider_review_hygiene.py +0 -0
  282. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_doctor_registry.py +0 -0
  283. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_local_llm_github_helpers.py +0 -0
  284. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_plan_context.py +0 -0
  285. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_provider_local_cli.py +0 -0
  286. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_provider_runners_comments.py +0 -0
  287. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_provider_runners_git.py +0 -0
  288. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_provider_runners_github_pr.py +0 -0
  289. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_provider_runners_pr_worktree.py +0 -0
  290. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_provider_runners_process.py +0 -0
  291. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_provider_runners_repo_paths.py +0 -0
  292. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_provider_runners_text_schema.py +0 -0
  293. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_provider_runners_workspace.py +0 -0
  294. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_reviewer_metrics_core.py +0 -0
  295. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_reviewer_spend.py +0 -0
  296. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_trailer_comment_labeler.py +0 -0
  297. {code_mower-0.5.0b42 → code_mower-0.5.0b43}/tests/test_work_orders.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-mower
3
- Version: 0.5.0b42
3
+ Version: 0.5.0b43
4
4
  Summary: Multi-reviewer AI code audit orchestration
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.11
@@ -103,11 +103,11 @@ Code Mower currently targets Python 3.11+; Python 3.12 is recommended.
103
103
 
104
104
  ```bash
105
105
  python3.12 --version
106
- pipx install --python python3.12 code-mower==0.5.0b42
106
+ pipx install --python python3.12 code-mower==0.5.0b43
107
107
  code-mower --version
108
108
  ```
109
109
 
110
- `0.5.0b42` is a beta release. Until Code Mower publishes a stable `1.0`
110
+ `0.5.0b43` is a beta release. Until Code Mower publishes a stable `1.0`
111
111
  line, use the explicit beta version above or allow prereleases with:
112
112
 
113
113
  ```bash
@@ -155,7 +155,7 @@ First-time command map: [docs/launch-command-surface.md](docs/launch-command-sur
155
155
 
156
156
  The current PyPI beta has been rehearsed end-to-end from a clean install:
157
157
  [First-User Install Rehearsal](docs/first-user-install-rehearsal.md) records
158
- the latest 10/10 public-package readiness proof for `code-mower==0.5.0b42`.
158
+ the latest 10/10 public-package readiness proof for `code-mower==0.5.0b43`.
159
159
 
160
160
  ## Optional: Plan Before Coding
161
161
 
@@ -310,9 +310,9 @@ builder provenance, start with
310
310
 
311
311
  ## Installation Status
312
312
 
313
- The current public beta is `v0.5.0-beta.42` from
313
+ The current public beta is `v0.5.0-beta.43` from
314
314
  [codemower-ai/code-mower](https://github.com/codemower-ai/code-mower), published
315
- as `code-mower==0.5.0b42` on [PyPI](https://pypi.org/project/code-mower/).
315
+ as `code-mower==0.5.0b43` on [PyPI](https://pypi.org/project/code-mower/).
316
316
  GitHub releases remain the auditable source for tags, build artifacts, and
317
317
  release notes.
318
318
 
@@ -88,11 +88,11 @@ Code Mower currently targets Python 3.11+; Python 3.12 is recommended.
88
88
 
89
89
  ```bash
90
90
  python3.12 --version
91
- pipx install --python python3.12 code-mower==0.5.0b42
91
+ pipx install --python python3.12 code-mower==0.5.0b43
92
92
  code-mower --version
93
93
  ```
94
94
 
95
- `0.5.0b42` is a beta release. Until Code Mower publishes a stable `1.0`
95
+ `0.5.0b43` is a beta release. Until Code Mower publishes a stable `1.0`
96
96
  line, use the explicit beta version above or allow prereleases with:
97
97
 
98
98
  ```bash
@@ -140,7 +140,7 @@ First-time command map: [docs/launch-command-surface.md](docs/launch-command-sur
140
140
 
141
141
  The current PyPI beta has been rehearsed end-to-end from a clean install:
142
142
  [First-User Install Rehearsal](docs/first-user-install-rehearsal.md) records
143
- the latest 10/10 public-package readiness proof for `code-mower==0.5.0b42`.
143
+ the latest 10/10 public-package readiness proof for `code-mower==0.5.0b43`.
144
144
 
145
145
  ## Optional: Plan Before Coding
146
146
 
@@ -295,9 +295,9 @@ builder provenance, start with
295
295
 
296
296
  ## Installation Status
297
297
 
298
- The current public beta is `v0.5.0-beta.42` from
298
+ The current public beta is `v0.5.0-beta.43` from
299
299
  [codemower-ai/code-mower](https://github.com/codemower-ai/code-mower), published
300
- as `code-mower==0.5.0b42` on [PyPI](https://pypi.org/project/code-mower/).
300
+ as `code-mower==0.5.0b43` on [PyPI](https://pypi.org/project/code-mower/).
301
301
  GitHub releases remain the auditable source for tags, build artifacts, and
302
302
  release notes.
303
303
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code-mower"
7
- version = "0.5.0b42"
7
+ version = "0.5.0b43"
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.0b42"
3
+ __version__ = "0.5.0b43"
@@ -112,6 +112,11 @@ MAX_SUMMARY_CHARS = 4_000
112
112
  MAX_FINDING_TITLE_CHARS = 300
113
113
  MAX_FINDING_FILE_CHARS = 500
114
114
  MAX_FINDING_DETAIL_CHARS = 4_000
115
+ MAX_CLAUDE_AUDIT_ATTEMPTS = 2
116
+ MIN_BLOCKED_VERDICT_PROSE_CHARS = 80
117
+ SCHEMA_PLACEHOLDER_TEXT = frozenset({"test", "example", "placeholder"})
118
+ CLAUDE_RAW_OUTPUT_SCHEMA = "code_mower.claudeAuditRawOutput.v1"
119
+ BLOCKER_SEVERITIES = frozenset({"P0", "P1", "P2"})
115
120
 
116
121
  STALE_TRAILER = "<!-- CLAUDE_AUDIT_STATE: needs-claude-audit -->"
117
122
  DONE_TRAILER = "<!-- CLAUDE_AUDIT_STATE: claude-audit-done -->"
@@ -181,6 +186,8 @@ class ClaudeVerdict:
181
186
  p2_count: int = 0
182
187
  p3_count: int = 0
183
188
  mismatch_note: str = ""
189
+ summary: str = ""
190
+ findings: Tuple[Dict[str, Any], ...] = field(default_factory=tuple)
184
191
 
185
192
  @property
186
193
  def blocker_count(self) -> int:
@@ -207,6 +214,7 @@ class ClaudeAuditResult:
207
214
  class DiffContext:
208
215
  stat: str
209
216
  diff: str
217
+ changed_files: Tuple[str, ...]
210
218
  was_truncated: bool
211
219
  requested_max_bytes: int
212
220
  hard_limit_bytes: int
@@ -337,7 +345,7 @@ def parse_structured_claude_verdict(data: Any) -> ClaudeVerdict:
337
345
 
338
346
  p_counts[int(severity[1])] += 1
339
347
  if len(rendered_findings) < MAX_RENDERED_FINDINGS:
340
- rendered_findings.append(raw_finding)
348
+ rendered_findings.append(dict(raw_finding))
341
349
 
342
350
  blocker_count = p_counts[0] + p_counts[1] + p_counts[2]
343
351
  if blocker_count > 0:
@@ -362,9 +370,139 @@ def parse_structured_claude_verdict(data: Any) -> ClaudeVerdict:
362
370
  p2_count=p_counts[2],
363
371
  p3_count=p_counts[3],
364
372
  mismatch_note=mismatch_note,
373
+ summary=summary,
374
+ findings=tuple(dict(finding) for finding in raw_findings),
365
375
  )
366
376
 
367
377
 
378
+ def _placeholder_equal(value: object) -> bool:
379
+ return str(value or "").strip().lower() in SCHEMA_PLACEHOLDER_TEXT
380
+
381
+
382
+ def _normalize_diff_path(value: object) -> str:
383
+ path = str(value or "").strip().replace("\\", "/")
384
+ while path.startswith("./"):
385
+ path = path[2:]
386
+ if path.startswith(("a/", "b/")):
387
+ path = path[2:]
388
+ return path
389
+
390
+
391
+ def _diff_file_matches(file_path: str, allowed_files: set[str]) -> bool:
392
+ if file_path in allowed_files:
393
+ return True
394
+ suffix_matches = [
395
+ path
396
+ for path in allowed_files
397
+ if path.endswith(f"/{file_path}") or file_path.endswith(f"/{path}")
398
+ ]
399
+ return len(suffix_matches) == 1
400
+
401
+
402
+ def _claude_verdict_guardrail_reason(
403
+ parsed: ClaudeVerdict,
404
+ changed_files: Tuple[str, ...],
405
+ ) -> Optional[str]:
406
+ if parsed.verdict == "UNKNOWN":
407
+ return None
408
+
409
+ if _placeholder_equal(parsed.summary):
410
+ return "structured verdict summary matched a schema placeholder value"
411
+
412
+ allowed_files = {_normalize_diff_path(path) for path in changed_files}
413
+ allowed_files.discard("")
414
+ for idx, finding in enumerate(parsed.findings):
415
+ where = f"findings[{idx}]"
416
+ if _placeholder_equal(finding.get("title")):
417
+ return f"{where}.title matched a schema placeholder value"
418
+ if _placeholder_equal(finding.get("detail")):
419
+ return f"{where}.detail matched a schema placeholder value"
420
+
421
+ file_path = _normalize_diff_path(finding.get("file"))
422
+ severity = str(finding.get("severity") or "")
423
+ if (
424
+ severity in BLOCKER_SEVERITIES
425
+ and (not file_path or not _diff_file_matches(file_path, allowed_files))
426
+ ):
427
+ return f"{where}.file {file_path!r} is not present in the PR diff"
428
+
429
+ if (
430
+ parsed.verdict == "BLOCKED"
431
+ and len(parsed.prose.strip()) < MIN_BLOCKED_VERDICT_PROSE_CHARS
432
+ ):
433
+ return "structured blocked verdict body is too short to be credible"
434
+
435
+ return None
436
+
437
+
438
+ def _apply_claude_verdict_guardrails(
439
+ parsed: ClaudeVerdict,
440
+ changed_files: Tuple[str, ...],
441
+ ) -> Tuple[ClaudeVerdict, Optional[str]]:
442
+ reason = _claude_verdict_guardrail_reason(parsed, changed_files)
443
+ if reason is None:
444
+ return parsed, None
445
+ return _unknown_structured_verdict(reason), reason
446
+
447
+
448
+ def _write_claude_raw_output_sidecar(
449
+ artifact_path: Optional[Path],
450
+ attempts: List[Dict[str, Any]],
451
+ ) -> None:
452
+ if artifact_path is None:
453
+ return
454
+ sidecar_path = artifact_path.with_suffix(".claude-raw-output.json")
455
+ payload = {"schema": CLAUDE_RAW_OUTPUT_SCHEMA, "attempts": attempts}
456
+ try:
457
+ text = json.dumps(payload, indent=2, sort_keys=True) + "\n"
458
+ sidecar_path.write_text(text, encoding="utf-8")
459
+ except OSError as exc:
460
+ print(
461
+ f"warning: failed to write Claude raw output sidecar {sidecar_path}: {exc}",
462
+ file=sys.stderr,
463
+ )
464
+
465
+
466
+ def _guardrail_retry_prompt(
467
+ prompt: str,
468
+ reason: str,
469
+ changed_files: Tuple[str, ...],
470
+ ) -> str:
471
+ files = "\n".join(f"- {path}" for path in changed_files[:50])
472
+ if len(changed_files) > 50:
473
+ files += f"\n- ... {len(changed_files) - 50} additional file(s) omitted"
474
+ return (
475
+ f"{prompt.rstrip()}\n\n"
476
+ "Trusted wrapper retry instruction:\n"
477
+ f"The previous structured verdict was rejected: {reason}. "
478
+ "Return a fresh JSON verdict only. Do not use placeholder values such "
479
+ "as 'test', 'example', or 'placeholder'. P0/P1/P2 findings must cite a "
480
+ "file from the PR diff, using the repository-relative path or a unique "
481
+ "suffix. PR diff files:\n"
482
+ f"{files}\n"
483
+ )
484
+
485
+
486
+ def _record_posted_comment_url(
487
+ artifact_path: Optional[Path],
488
+ posted_comment_url: Optional[str],
489
+ ) -> None:
490
+ if artifact_path is None or not posted_comment_url:
491
+ return
492
+ try:
493
+ payload = json.loads(artifact_path.read_text(encoding="utf-8"))
494
+ if not isinstance(payload, dict):
495
+ return
496
+ payload["posted_comment_url"] = posted_comment_url
497
+ text = json.dumps(payload, indent=2, sort_keys=True) + "\n"
498
+ artifact_path.write_text(text, encoding="utf-8")
499
+ except (OSError, json.JSONDecodeError) as exc:
500
+ print(
501
+ f"warning: failed to update audit verdict artifact {artifact_path}: {exc}",
502
+ file=sys.stderr,
503
+ )
504
+
505
+
368
506
  def _claude_env() -> Dict[str, str]:
369
507
  preserve_auth_overrides = env_flag(os.environ.get("CODE_MOWER_CLAUDE_KEEP_AUTH_ENV"))
370
508
  env, _removed = clean_claude_cli_env(
@@ -505,6 +643,13 @@ def _build_diff_context(
505
643
  raise FetchedHeadMismatch(expected_head_sha, fetched_head_ref)
506
644
  diff_range = f"{fetched_base_ref}...{fetched_head_ref}"
507
645
  stat = _run_git(local_repo, ["diff", "--stat", "--find-renames", diff_range])
646
+ changed_files_text = _run_git(
647
+ local_repo,
648
+ ["-c", "core.quotePath=false", "diff", "--name-only", "--find-renames", diff_range],
649
+ )
650
+ changed_files = tuple(
651
+ line.strip() for line in changed_files_text.splitlines() if line.strip()
652
+ )
508
653
  included_diff, full_diff_bytes, was_truncated = _run_git_limited(
509
654
  local_repo,
510
655
  ["diff", "--find-renames", "--unified=80", diff_range],
@@ -518,6 +663,7 @@ def _build_diff_context(
518
663
  return DiffContext(
519
664
  stat=stat,
520
665
  diff=included_diff,
666
+ changed_files=changed_files,
521
667
  was_truncated=was_truncated,
522
668
  requested_max_bytes=max_diff_bytes,
523
669
  hard_limit_bytes=hard_limit,
@@ -869,6 +1015,10 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
869
1015
  )
870
1016
  posted = post_pr_comment(repo, pr_number, comment_body, token=config.github_token)
871
1017
  result.posted_comment_url = posted.get("html_url")
1018
+ _record_posted_comment_url(
1019
+ result.verdict_artifact_path,
1020
+ result.posted_comment_url,
1021
+ )
872
1022
  print(
873
1023
  f"posted {repo}#{pr_number} verdict=STALE "
874
1024
  f"url={result.posted_comment_url}",
@@ -927,8 +1077,46 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
927
1077
  ),
928
1078
  )
929
1079
 
1080
+ raw_output_attempts: List[Dict[str, Any]] = []
1081
+ parsed = _unknown_structured_verdict("Claude audit did not run")
1082
+ claude_stdout = ""
1083
+ claude_stderr = ""
1084
+ attempt_prompt = prompt
930
1085
  t0 = time.time()
931
- parsed, claude_stdout, claude_stderr = run_claude_audit(config, prompt)
1086
+ for attempt in range(1, MAX_CLAUDE_AUDIT_ATTEMPTS + 1):
1087
+ parsed_candidate, attempt_stdout, attempt_stderr = run_claude_audit(
1088
+ config,
1089
+ attempt_prompt,
1090
+ )
1091
+ claude_stdout = attempt_stdout
1092
+ claude_stderr = attempt_stderr
1093
+ parsed, guardrail_reason = _apply_claude_verdict_guardrails(
1094
+ parsed_candidate,
1095
+ diff_context.changed_files,
1096
+ )
1097
+ raw_output_attempts.append(
1098
+ {
1099
+ "attempt": attempt,
1100
+ "stdout": attempt_stdout,
1101
+ "stderr": attempt_stderr,
1102
+ "verdict": parsed_candidate.verdict,
1103
+ "guardrail_rejection": guardrail_reason,
1104
+ }
1105
+ )
1106
+ if guardrail_reason and attempt < MAX_CLAUDE_AUDIT_ATTEMPTS:
1107
+ print(
1108
+ " structured-verdict guardrail rejected Claude output: "
1109
+ f"{guardrail_reason}; retrying once",
1110
+ file=sys.stderr,
1111
+ flush=True,
1112
+ )
1113
+ attempt_prompt = _guardrail_retry_prompt(
1114
+ prompt,
1115
+ guardrail_reason,
1116
+ diff_context.changed_files,
1117
+ )
1118
+ continue
1119
+ break
932
1120
  dt = time.time() - t0
933
1121
  print(f" claude audit completed in {dt:.0f}s", file=sys.stderr, flush=True)
934
1122
  if parsed.mismatch_note:
@@ -1001,8 +1189,13 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
1001
1189
  file=sys.stderr,
1002
1190
  flush=True,
1003
1191
  )
1192
+ _write_claude_raw_output_sidecar(artifact_path, raw_output_attempts)
1004
1193
  posted = post_pr_comment(repo, pr_number, comment_body, token=config.github_token)
1005
1194
  result.posted_comment_url = posted.get("html_url")
1195
+ _record_posted_comment_url(
1196
+ result.verdict_artifact_path,
1197
+ result.posted_comment_url,
1198
+ )
1006
1199
  print(
1007
1200
  f"posted {repo}#{pr_number} verdict={result_verdict} "
1008
1201
  f"url={result.posted_comment_url}",
@@ -14,7 +14,7 @@ from pathlib import Path
14
14
  if __package__ in {None, ""}:
15
15
  raise SystemExit(
16
16
  "code_mower.cli is a packaged entrypoint. Install Code Mower with "
17
- "`pipx install code-mower==0.5.0b42`, or run source checkouts with "
17
+ "`pipx install code-mower==0.5.0b43`, or run source checkouts with "
18
18
  "`PYTHONPATH=src python -m code_mower.cli`."
19
19
  )
20
20
 
@@ -8,7 +8,7 @@ permissions:
8
8
  contents: read
9
9
 
10
10
  env:
11
- CODE_MOWER_PACKAGE_SPEC: code-mower==0.5.0b42
11
+ CODE_MOWER_PACKAGE_SPEC: code-mower==0.5.0b43
12
12
 
13
13
  jobs:
14
14
  auto-record:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-mower
3
- Version: 0.5.0b42
3
+ Version: 0.5.0b43
4
4
  Summary: Multi-reviewer AI code audit orchestration
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.11
@@ -103,11 +103,11 @@ Code Mower currently targets Python 3.11+; Python 3.12 is recommended.
103
103
 
104
104
  ```bash
105
105
  python3.12 --version
106
- pipx install --python python3.12 code-mower==0.5.0b42
106
+ pipx install --python python3.12 code-mower==0.5.0b43
107
107
  code-mower --version
108
108
  ```
109
109
 
110
- `0.5.0b42` is a beta release. Until Code Mower publishes a stable `1.0`
110
+ `0.5.0b43` is a beta release. Until Code Mower publishes a stable `1.0`
111
111
  line, use the explicit beta version above or allow prereleases with:
112
112
 
113
113
  ```bash
@@ -155,7 +155,7 @@ First-time command map: [docs/launch-command-surface.md](docs/launch-command-sur
155
155
 
156
156
  The current PyPI beta has been rehearsed end-to-end from a clean install:
157
157
  [First-User Install Rehearsal](docs/first-user-install-rehearsal.md) records
158
- the latest 10/10 public-package readiness proof for `code-mower==0.5.0b42`.
158
+ the latest 10/10 public-package readiness proof for `code-mower==0.5.0b43`.
159
159
 
160
160
  ## Optional: Plan Before Coding
161
161
 
@@ -310,9 +310,9 @@ builder provenance, start with
310
310
 
311
311
  ## Installation Status
312
312
 
313
- The current public beta is `v0.5.0-beta.42` from
313
+ The current public beta is `v0.5.0-beta.43` from
314
314
  [codemower-ai/code-mower](https://github.com/codemower-ai/code-mower), published
315
- as `code-mower==0.5.0b42` on [PyPI](https://pypi.org/project/code-mower/).
315
+ as `code-mower==0.5.0b43` on [PyPI](https://pypi.org/project/code-mower/).
316
316
  GitHub releases remain the auditable source for tags, build artifacts, and
317
317
  release notes.
318
318
 
@@ -257,6 +257,7 @@ tests/test_builder_runs.py
257
257
  tests/test_calibration_effect_report.py
258
258
  tests/test_calibration_truth.py
259
259
  tests/test_checks.py
260
+ tests/test_claude_audit_pr.py
260
261
  tests/test_claude_cli_bounce.py
261
262
  tests/test_clear_stale.py
262
263
  tests/test_cloud_client.py
@@ -0,0 +1,175 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import tempfile
5
+ import unittest
6
+ from pathlib import Path
7
+ from unittest import mock
8
+
9
+ from code_mower import claude_audit_pr as cap
10
+
11
+
12
+ def _finding(**overrides: object) -> dict[str, object]:
13
+ return {
14
+ "severity": "P1",
15
+ "title": "Real blocker",
16
+ "file": "src/app.py",
17
+ "line": 12,
18
+ "detail": "This explains a concrete regression with enough context.",
19
+ **overrides,
20
+ }
21
+
22
+
23
+ def _payload(**overrides: object) -> dict[str, object]:
24
+ return {
25
+ "schema": cap.CLAUDE_AUDIT_SCHEMA_ID,
26
+ "verdict": "blocked",
27
+ "summary": "Real audit summary with enough detail.",
28
+ "findings": [_finding()],
29
+ **overrides,
30
+ }
31
+
32
+
33
+ class ClaudeAuditPrTests(unittest.TestCase):
34
+ def test_claude_guardrails_reject_untrusted_verdicts(self) -> None:
35
+ cases = [
36
+ (
37
+ cap.parse_structured_claude_verdict(_payload(summary="test")),
38
+ ("src/app.py",),
39
+ "structured verdict summary matched a schema placeholder value",
40
+ ),
41
+ (
42
+ cap.parse_structured_claude_verdict(
43
+ _payload(findings=[_finding(file="a.txt")])
44
+ ),
45
+ ("src/app.py",),
46
+ "is not present in the PR diff",
47
+ ),
48
+ (
49
+ cap.ClaudeVerdict(
50
+ verdict="BLOCKED",
51
+ prose="too short",
52
+ findings=(
53
+ {"title": "Real title", "file": "src/app.py", "detail": "Real"},
54
+ ),
55
+ ),
56
+ ("src/app.py",),
57
+ "structured blocked verdict body is too short to be credible",
58
+ ),
59
+ ]
60
+
61
+ for parsed, changed_files, expected in cases:
62
+ with self.subTest(expected=expected):
63
+ guarded, reason = cap._apply_claude_verdict_guardrails(
64
+ parsed,
65
+ changed_files,
66
+ )
67
+ self.assertEqual(guarded.verdict, "UNKNOWN")
68
+ self.assertIn(expected, str(reason))
69
+
70
+ def test_claude_guardrails_accept_path_variants_and_p3_misses(self) -> None:
71
+ parsed = cap.parse_structured_claude_verdict(
72
+ _payload(
73
+ findings=[
74
+ _finding(file="b/src/app.py"),
75
+ _finding(severity="P3", file="notes.md"),
76
+ ]
77
+ )
78
+ )
79
+
80
+ guarded, reason = cap._apply_claude_verdict_guardrails(parsed, ("src/app.py",))
81
+
82
+ self.assertEqual(guarded.verdict, "BLOCKED")
83
+ self.assertIsNone(reason)
84
+
85
+ def test_claude_audit_retries_guardrail_rejection_and_updates_artifact(self) -> None:
86
+ with tempfile.TemporaryDirectory() as tmp:
87
+ tmp_path = Path(tmp)
88
+ repo = tmp_path / "repo"
89
+ repo.mkdir()
90
+ head_sha = "a" * 40
91
+ pr_payload = {"head": {"sha": head_sha, "ref": "human/fix"}, "title": "Fix"}
92
+ placeholder = cap.parse_structured_claude_verdict(
93
+ _payload(summary="test", findings=[_finding(title="test")])
94
+ )
95
+ clean = cap.parse_structured_claude_verdict(
96
+ {
97
+ "schema": cap.CLAUDE_AUDIT_SCHEMA_ID,
98
+ "verdict": "pass",
99
+ "summary": "No merge-blocking regressions found in this review.",
100
+ "findings": [],
101
+ }
102
+ )
103
+ diff_context = cap.DiffContext(
104
+ "src/app.py | 1 +",
105
+ "diff --git a/src/app.py b/src/app.py",
106
+ ("src/app.py",),
107
+ False,
108
+ 1_000,
109
+ 1_000,
110
+ 40,
111
+ 40,
112
+ )
113
+ config = cap.ClaudeAuditConfig(
114
+ "token",
115
+ {"owner/repo": repo},
116
+ include_plan_context=False,
117
+ )
118
+
119
+ with (
120
+ mock.patch.dict(
121
+ "os.environ",
122
+ {"CODE_MOWER_VERDICT_ARTIFACT_DIR": str(tmp_path / "verdicts")},
123
+ ),
124
+ mock.patch.object(
125
+ cap,
126
+ "fetch_pull_request",
127
+ side_effect=[pr_payload, pr_payload],
128
+ ),
129
+ mock.patch.object(cap, "_build_diff_context", return_value=diff_context),
130
+ mock.patch.object(
131
+ cap.code_mower_prompts,
132
+ "load_review_prompt",
133
+ return_value="",
134
+ ),
135
+ mock.patch.object(
136
+ cap,
137
+ "run_claude_audit",
138
+ side_effect=[
139
+ (placeholder, '{"structured_output":"placeholder"}', ""),
140
+ (clean, '{"structured_output":"pass"}', ""),
141
+ ],
142
+ ) as run_claude,
143
+ mock.patch.object(
144
+ cap,
145
+ "post_pr_comment",
146
+ return_value={"html_url": "https://github.test/comment/1"},
147
+ ),
148
+ ):
149
+ result = cap.audit_pr(config, "owner/repo", 42)
150
+
151
+ self.assertEqual(result.verdict, "PASS")
152
+ self.assertEqual(run_claude.call_count, 2)
153
+ self.assertIn(
154
+ "Trusted wrapper retry instruction",
155
+ run_claude.call_args_list[1].args[1],
156
+ )
157
+ self.assertEqual(result.posted_comment_url, "https://github.test/comment/1")
158
+ self.assertIsNotNone(result.verdict_artifact_path)
159
+ assert result.verdict_artifact_path is not None
160
+
161
+ artifact = json.loads(result.verdict_artifact_path.read_text(encoding="utf-8"))
162
+ self.assertEqual(
163
+ artifact["posted_comment_url"],
164
+ "https://github.test/comment/1",
165
+ )
166
+
167
+ sidecar = result.verdict_artifact_path.with_suffix(".claude-raw-output.json")
168
+ payload = json.loads(sidecar.read_text(encoding="utf-8"))
169
+ self.assertEqual(payload["schema"], cap.CLAUDE_RAW_OUTPUT_SCHEMA)
170
+ self.assertTrue(payload["attempts"][0]["guardrail_rejection"])
171
+ self.assertIsNone(payload["attempts"][1]["guardrail_rejection"])
172
+
173
+
174
+ if __name__ == "__main__":
175
+ unittest.main()