code-mower 0.5.0b42__tar.gz → 0.5.0b44__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 (299) hide show
  1. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/PKG-INFO +6 -6
  2. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/README.md +5 -5
  3. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/pyproject.toml +1 -1
  4. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/__init__.py +1 -1
  5. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/auto_discovery.py +3 -0
  6. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/claude_audit_pr.py +286 -17
  7. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/cli.py +1 -1
  8. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/code_mower_telemetry.py +10 -2
  9. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/codex_audit_pr.py +95 -9
  10. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/provider_runners/__init__.py +10 -0
  11. code_mower-0.5.0b44/src/code_mower/provider_runners/verdict_artifacts.py +241 -0
  12. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/workflows/builder-provenance.yml.j2 +1 -1
  13. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower.egg-info/PKG-INFO +6 -6
  14. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower.egg-info/SOURCES.txt +2 -0
  15. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_calibration_truth.py +48 -0
  16. code_mower-0.5.0b44/tests/test_claude_audit_pr.py +319 -0
  17. code_mower-0.5.0b44/tests/test_codex_audit_pr.py +166 -0
  18. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_release_hygiene.py +34 -34
  19. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_reviewer_metrics_core.py +93 -0
  20. code_mower-0.5.0b42/src/code_mower/provider_runners/verdict_artifacts.py +0 -103
  21. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/CODE_OF_CONDUCT.md +0 -0
  22. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/LICENSE +0 -0
  23. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/MANIFEST.in +0 -0
  24. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/NOTICE +0 -0
  25. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/SUPPORT.md +0 -0
  26. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/requirements/requirements.txt +0 -0
  27. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/setup.cfg +0 -0
  28. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/adapters/__init__.py +0 -0
  29. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/adapters/_base.py +0 -0
  30. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/adapters/cursor_bugbot.py +0 -0
  31. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/adapters/gitar.py +0 -0
  32. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/adapters/greptile.py +0 -0
  33. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/adapters/qodo.py +0 -0
  34. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/antigravity_cli_audit_pr.py +0 -0
  35. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/audit_handoff_log.py +0 -0
  36. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/audit_labeler_lib.py +0 -0
  37. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/audit_progress.py +0 -0
  38. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/blind_review_artifacts.py +0 -0
  39. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/blind_review_coordinator.py +0 -0
  40. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/bootstrap.py +0 -0
  41. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/builder_experiment.py +0 -0
  42. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/builder_runs.py +0 -0
  43. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/__init__.py +0 -0
  44. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/arms.py +0 -0
  45. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/commands.py +0 -0
  46. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/context_inputs.py +0 -0
  47. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/corpus.py +0 -0
  48. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/effect_report.py +0 -0
  49. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/evidence.py +0 -0
  50. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/evidence_report.py +0 -0
  51. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/identity.py +0 -0
  52. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/metrics.py +0 -0
  53. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/overlap.py +0 -0
  54. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/planning.py +0 -0
  55. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/policy.py +0 -0
  56. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/results.py +0 -0
  57. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/run_results.py +0 -0
  58. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/run_status.py +0 -0
  59. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/runner.py +0 -0
  60. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/truth.py +0 -0
  61. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/calibration/value_report.py +0 -0
  62. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/checks.py +0 -0
  63. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/claude_cli_bounce.py +0 -0
  64. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/claude_cli_environment.py +0 -0
  65. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/clear_stale.py +0 -0
  66. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/cloud.py +0 -0
  67. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/cloud_client/__init__.py +0 -0
  68. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/cloud_client/bundle.py +0 -0
  69. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/cloud_client/doctor.py +0 -0
  70. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/cloud_client/dogfood.py +0 -0
  71. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/cloud_client/endpoints.py +0 -0
  72. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/cloud_client/errors.py +0 -0
  73. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/cloud_client/events.py +0 -0
  74. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/cloud_client/export.py +0 -0
  75. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/cloud_client/git_metadata.py +0 -0
  76. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/cloud_client/manifest.py +0 -0
  77. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/cloud_client/operations.py +0 -0
  78. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/cloud_client/reports.py +0 -0
  79. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/cloud_client/setup.py +0 -0
  80. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/cloud_client/upload.py +0 -0
  81. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/code_mower_calibration.py +0 -0
  82. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/code_mower_context_packs.py +0 -0
  83. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/code_mower_merge.py +0 -0
  84. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/coderabbit_cli_audit_pr.py +0 -0
  85. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/codex_audit_env_preflight.py +0 -0
  86. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/codex_audit_schema_smoke.py +0 -0
  87. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/codex_audit_verdict.schema.json +0 -0
  88. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/config.py +0 -0
  89. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor.py +0 -0
  90. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/__init__.py +0 -0
  91. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/cloud.py +0 -0
  92. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/common.py +0 -0
  93. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/github.py +0 -0
  94. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/github_actions.py +0 -0
  95. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/github_actions_cost.py +0 -0
  96. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/github_actions_cost_summary.py +0 -0
  97. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/github_actions_failure_annotations.py +0 -0
  98. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/github_actions_failure_models.py +0 -0
  99. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/github_actions_failure_scan.py +0 -0
  100. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/github_actions_failure_selection.py +0 -0
  101. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/github_actions_failures.py +0 -0
  102. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/github_actions_permissions.py +0 -0
  103. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/github_api.py +0 -0
  104. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/github_branch.py +0 -0
  105. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/github_config.py +0 -0
  106. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/github_provider.py +0 -0
  107. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/github_repo.py +0 -0
  108. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/groups.py +0 -0
  109. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/models.py +0 -0
  110. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/output.py +0 -0
  111. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/presets.py +0 -0
  112. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/privacy.py +0 -0
  113. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/provider_api_model.py +0 -0
  114. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/provider_api_model_openai.py +0 -0
  115. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/provider_api_model_profiles.py +0 -0
  116. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/provider_env.py +0 -0
  117. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/provider_env_required.py +0 -0
  118. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/provider_env_tokens.py +0 -0
  119. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/provider_local_cli.py +0 -0
  120. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/provider_local_cli_commands.py +0 -0
  121. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/provider_local_cli_probe_config.py +0 -0
  122. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/provider_probe.py +0 -0
  123. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/provider_probe_auth.py +0 -0
  124. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/provider_probe_evaluation.py +0 -0
  125. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/provider_probe_json.py +0 -0
  126. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/provider_probe_remediation.py +0 -0
  127. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/provider_review_hygiene.py +0 -0
  128. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/providers.py +0 -0
  129. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/registry.py +0 -0
  130. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/runner.py +0 -0
  131. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/runtime.py +0 -0
  132. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/doctor_checks/runtime_github_auth.py +0 -0
  133. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/gemini_cli_audit_pr.py +0 -0
  134. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/grok_build_audit_pr.py +0 -0
  135. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/hermes_cli_audit_pr.py +0 -0
  136. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/init.py +0 -0
  137. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/lane_configs/__init__.py +0 -0
  138. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/lane_configs/aider.py +0 -0
  139. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/lane_configs/antigravity_cli.py +0 -0
  140. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/lane_configs/claude.py +0 -0
  141. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/lane_configs/codex.py +0 -0
  142. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/lane_configs/devin.py +0 -0
  143. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/lane_configs/gemini_cli.py +0 -0
  144. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/lane_configs/grok_build.py +0 -0
  145. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/lane_configs/hermes_cli.py +0 -0
  146. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/lane_configs/local_llm.py +0 -0
  147. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/local_llm_audit_pr.py +0 -0
  148. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/local_llm_bakeoff.py +0 -0
  149. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/local_llm_calibration.py +0 -0
  150. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/local_llm_profiles.py +0 -0
  151. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/migration.py +0 -0
  152. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/migration_install.py +0 -0
  153. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/migration_mirror.py +0 -0
  154. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/migration_readiness.py +0 -0
  155. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/migration_rehearsal.py +0 -0
  156. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/next_steps.py +0 -0
  157. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/package.py +0 -0
  158. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/package_content.py +0 -0
  159. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/package_manifest.py +0 -0
  160. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/package_paths.py +0 -0
  161. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/package_rendering.py +0 -0
  162. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/package_static.py +0 -0
  163. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/plan_context.py +0 -0
  164. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/prompts.py +0 -0
  165. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/provider_registry.py +0 -0
  166. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/provider_runners/comments.py +0 -0
  167. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/provider_runners/git.py +0 -0
  168. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/provider_runners/github_auth.py +0 -0
  169. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/provider_runners/github_pr.py +0 -0
  170. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/provider_runners/pr_worktree.py +0 -0
  171. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/provider_runners/process.py +0 -0
  172. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/provider_runners/repo_paths.py +0 -0
  173. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/provider_runners/text_schema.py +0 -0
  174. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/provider_runners/workspace.py +0 -0
  175. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/providers/__init__.py +0 -0
  176. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/providers/local_cli.py +0 -0
  177. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/providers/provenance.py +0 -0
  178. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/release_readiness.py +0 -0
  179. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/reviewer_metrics.py +0 -0
  180. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/reviewer_spend.py +0 -0
  181. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/saas_reviewer_labeler.py +0 -0
  182. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/secrets.py +0 -0
  183. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/builder-experiment.example.json +0 -0
  184. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/calibration-corpus.example.json +0 -0
  185. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/calibration-corpus.json +0 -0
  186. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/code-mower.example.yml +0 -0
  187. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/context-packs.example.json +0 -0
  188. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/lane_prompts/base-audit.md +0 -0
  189. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/lane_prompts/calibration-policy.md +0 -0
  190. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/lane_prompts/context-driven-quality.md +0 -0
  191. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/lane_prompts/docs-design.md +0 -0
  192. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/lane_prompts/generic-programming.md +0 -0
  193. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/lane_prompts/operability.md +0 -0
  194. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/lane_prompts/package-runtime.md +0 -0
  195. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/lane_prompts/plan-conformance.md +0 -0
  196. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/lane_prompts/security-threat-model.md +0 -0
  197. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/product-support/code_mower +0 -0
  198. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/product-support/code_mower_standalone_pin.env +0 -0
  199. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/product-support/code_mower_standalone_shadow.sh +0 -0
  200. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/product-support/run_claude_audit_pr.sh +0 -0
  201. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/product-support/run_codex_audit_pr.sh +0 -0
  202. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/product-support/safe_gh_comment.py +0 -0
  203. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/product-support/status_report.py +0 -0
  204. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/providers.yml +0 -0
  205. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/reviewer-spend.example.json +0 -0
  206. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/reviewer-value-report.example.md +0 -0
  207. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/workflows/audit-label-cleanup.yml.j2 +0 -0
  208. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/workflows/code-mower-gate-health.yml.j2 +0 -0
  209. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/workflows/code-mower-gate.yml.j2 +0 -0
  210. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/workflows/hosted-bridge.yml.j2 +0 -0
  211. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/workflows/local-cli-audit.yml.j2 +0 -0
  212. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/workflows/needs-owner-notify.yml.j2 +0 -0
  213. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/workflows/private-standalone-shadow.yml.j2 +0 -0
  214. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/workflows/review-clear-stale.yml.j2 +0 -0
  215. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/workflows/saas-reviewer-labeler.yml.j2 +0 -0
  216. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/workflows/self-hosted-local-audit.yml.j2 +0 -0
  217. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/workflows/trailer-comment-labeler.yml.j2 +0 -0
  218. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/templates/workflows/weekly-status.yml.j2 +0 -0
  219. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/trailer_comment_labeler.py +0 -0
  220. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/versioning.py +0 -0
  221. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower/work_orders.py +0 -0
  222. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower.egg-info/dependency_links.txt +0 -0
  223. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower.egg-info/entry_points.txt +0 -0
  224. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower.egg-info/requires.txt +0 -0
  225. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/src/code_mower.egg-info/top_level.txt +0 -0
  226. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/builder-experiment.example.json +0 -0
  227. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/calibration-corpus.example.json +0 -0
  228. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/calibration-corpus.json +0 -0
  229. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/code-mower.yml.j2 +0 -0
  230. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/context-packs.example.json +0 -0
  231. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/cursor/BUGBOT.md +0 -0
  232. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/providers/acp_bridge.yml +0 -0
  233. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/providers/aider.yml +0 -0
  234. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/providers/antigravity_cli.yml +0 -0
  235. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/providers/claude_audit.yml +0 -0
  236. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/providers/claude_review.yml +0 -0
  237. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/providers/coderabbit_cli.yml +0 -0
  238. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/providers/codex.yml +0 -0
  239. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/providers/cursor_bugbot.yml +0 -0
  240. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/providers/devin.yml +0 -0
  241. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/providers/gemini_cli.yml +0 -0
  242. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/providers/gitar.yml +0 -0
  243. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/providers/greptile.yml +0 -0
  244. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/providers/hermes_cli.yml +0 -0
  245. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/providers/local_llm.yml +0 -0
  246. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/providers/qodo.yml +0 -0
  247. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/providers.yml +0 -0
  248. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/reviewer-spend.example.json +0 -0
  249. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/reviewer-value-report.example.md +0 -0
  250. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/workflows/audit-label-cleanup.yml.j2 +0 -0
  251. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/workflows/blind-review-artifacts-dry-run.yml.j2 +0 -0
  252. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/workflows/builder-provenance.yml.j2 +0 -0
  253. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/workflows/code-mower-gate-health.yml.j2 +0 -0
  254. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/workflows/code-mower-gate.yml.j2 +0 -0
  255. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/workflows/hosted-bridge.yml.j2 +0 -0
  256. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/workflows/local-cli-audit.yml.j2 +0 -0
  257. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/workflows/private-standalone-shadow.yml.j2 +0 -0
  258. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/workflows/review-clear-stale.yml.j2 +0 -0
  259. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/workflows/saas-reviewer-labeler.yml.j2 +0 -0
  260. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/workflows/self-hosted-local-audit.yml.j2 +0 -0
  261. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/templates/workflows/trailer-comment-labeler.yml.j2 +0 -0
  262. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_audit_comment_posture.py +0 -0
  263. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_builder_identity.py +0 -0
  264. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_builder_runs.py +0 -0
  265. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_calibration_effect_report.py +0 -0
  266. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_checks.py +0 -0
  267. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_claude_cli_bounce.py +0 -0
  268. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_clear_stale.py +0 -0
  269. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_cloud_client.py +0 -0
  270. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_cloud_git_metadata.py +0 -0
  271. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_cloud_manifest.py +0 -0
  272. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_cloud_reports.py +0 -0
  273. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_coderabbit_cli_audit_pr.py +0 -0
  274. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_codex_audit_env_preflight.py +0 -0
  275. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_doctor_actions_cost_summary.py +0 -0
  276. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_doctor_actions_failure_selection.py +0 -0
  277. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_doctor_boundaries.py +0 -0
  278. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_doctor_github_checks.py +0 -0
  279. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_doctor_output.py +0 -0
  280. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_doctor_privacy.py +0 -0
  281. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_doctor_provider_env_required.py +0 -0
  282. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_doctor_provider_env_tokens.py +0 -0
  283. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_doctor_provider_probe.py +0 -0
  284. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_doctor_provider_review_hygiene.py +0 -0
  285. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_doctor_registry.py +0 -0
  286. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_local_llm_github_helpers.py +0 -0
  287. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_plan_context.py +0 -0
  288. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_provider_local_cli.py +0 -0
  289. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_provider_runners_comments.py +0 -0
  290. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_provider_runners_git.py +0 -0
  291. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_provider_runners_github_pr.py +0 -0
  292. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_provider_runners_pr_worktree.py +0 -0
  293. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_provider_runners_process.py +0 -0
  294. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_provider_runners_repo_paths.py +0 -0
  295. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_provider_runners_text_schema.py +0 -0
  296. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_provider_runners_workspace.py +0 -0
  297. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_reviewer_spend.py +0 -0
  298. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/tests/test_trailer_comment_labeler.py +0 -0
  299. {code_mower-0.5.0b42 → code_mower-0.5.0b44}/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.0b44
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.0b44
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.0b44` 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.0b44`.
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.44` 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.0b44` 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.0b44
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.0b44` 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.0b44`.
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.44` 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.0b44` 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.0b44"
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.0b44"
@@ -12,6 +12,7 @@ from typing import Any, Mapping, Sequence
12
12
  from .corpus import parse_int
13
13
  from .identity import safe_slug
14
14
  from .run_status import RUN_STATUS_BLOCKED, RUN_STATUS_PASS, RUN_STATUS_UNKNOWN
15
+ from ..provider_runners import is_fixture_verdict_comment
15
16
 
16
17
  AUTO_DISCOVERY_SCHEMA = "code_mower.calibrationAutoDiscover.v1"
17
18
  TRUTH_EXPECTATION_UNKNOWN = "unknown"
@@ -147,6 +148,8 @@ def _audit_runs_from_comments(comments: Sequence[Any]) -> list[dict[str, Any]]:
147
148
  body = str(comment.get("body") or "")
148
149
  if not body:
149
150
  continue
151
+ if is_fixture_verdict_comment(body):
152
+ continue
150
153
  for match in AUDIT_STATE_RE.finditer(body):
151
154
  state = match.group("state")
152
155
  reviewer = _provider_from_audit_state(match.group("provider"), state)
@@ -32,11 +32,13 @@ if __package__ in {None, "", "tools"}:
32
32
  from tools.audit_progress import AuditProgress, run_subprocess_with_progress
33
33
  from tools.claude_cli_environment import clean_claude_cli_env, env_flag
34
34
  from tools.provider_runners import (
35
+ audit_runtime_quarantine_reason as _audit_runtime_quarantine_reason,
35
36
  clip_text as _clip_text,
36
37
  fetch_pull_request,
37
38
  fetch_base_ref_sha as _shared_fetch_base_ref_sha,
38
39
  fetch_pr_head_sha as _shared_fetch_pr_head_sha,
39
40
  fetch_pr_head_sha_unless_local_matches as _shared_fetch_pr_head_sha_unless_local_matches,
41
+ is_fixture_structured_verdict as _is_fixture_structured_verdict,
40
42
  limit_comment_body,
41
43
  one_line as _one_line,
42
44
  parse_repo_paths as _parse_repo_paths,
@@ -58,11 +60,13 @@ if __package__ in {None, "", "tools"}:
58
60
  from audit_progress import AuditProgress, run_subprocess_with_progress # type: ignore
59
61
  from claude_cli_environment import clean_claude_cli_env, env_flag # type: ignore
60
62
  from provider_runners import ( # type: ignore
63
+ audit_runtime_quarantine_reason as _audit_runtime_quarantine_reason,
61
64
  clip_text as _clip_text,
62
65
  fetch_pull_request,
63
66
  fetch_base_ref_sha as _shared_fetch_base_ref_sha,
64
67
  fetch_pr_head_sha as _shared_fetch_pr_head_sha,
65
68
  fetch_pr_head_sha_unless_local_matches as _shared_fetch_pr_head_sha_unless_local_matches,
69
+ is_fixture_structured_verdict as _is_fixture_structured_verdict,
66
70
  limit_comment_body,
67
71
  one_line as _one_line,
68
72
  parse_repo_paths as _parse_repo_paths,
@@ -82,11 +86,13 @@ else: # pragma: no cover - exercised after package extraction.
82
86
  from .claude_cli_environment import clean_claude_cli_env, env_flag
83
87
  from .provider_runners import (
84
88
  FetchedHeadMismatch,
89
+ audit_runtime_quarantine_reason as _audit_runtime_quarantine_reason,
85
90
  clip_text as _clip_text,
86
91
  fetch_base_ref_sha as _shared_fetch_base_ref_sha,
87
92
  fetch_pull_request,
88
93
  fetch_pr_head_sha as _shared_fetch_pr_head_sha,
89
94
  fetch_pr_head_sha_unless_local_matches as _shared_fetch_pr_head_sha_unless_local_matches,
95
+ is_fixture_structured_verdict as _is_fixture_structured_verdict,
90
96
  limit_comment_body,
91
97
  one_line as _one_line,
92
98
  parse_repo_paths as _parse_repo_paths,
@@ -112,6 +118,11 @@ MAX_SUMMARY_CHARS = 4_000
112
118
  MAX_FINDING_TITLE_CHARS = 300
113
119
  MAX_FINDING_FILE_CHARS = 500
114
120
  MAX_FINDING_DETAIL_CHARS = 4_000
121
+ MAX_CLAUDE_AUDIT_ATTEMPTS = 2
122
+ MIN_BLOCKED_VERDICT_PROSE_CHARS = 80
123
+ SCHEMA_PLACEHOLDER_TEXT = frozenset({"test", "example", "placeholder"})
124
+ CLAUDE_RAW_OUTPUT_SCHEMA = "code_mower.claudeAuditRawOutput.v1"
125
+ BLOCKER_SEVERITIES = frozenset({"P0", "P1", "P2"})
115
126
 
116
127
  STALE_TRAILER = "<!-- CLAUDE_AUDIT_STATE: needs-claude-audit -->"
117
128
  DONE_TRAILER = "<!-- CLAUDE_AUDIT_STATE: claude-audit-done -->"
@@ -181,6 +192,8 @@ class ClaudeVerdict:
181
192
  p2_count: int = 0
182
193
  p3_count: int = 0
183
194
  mismatch_note: str = ""
195
+ summary: str = ""
196
+ findings: Tuple[Dict[str, Any], ...] = field(default_factory=tuple)
184
197
 
185
198
  @property
186
199
  def blocker_count(self) -> int:
@@ -207,6 +220,7 @@ class ClaudeAuditResult:
207
220
  class DiffContext:
208
221
  stat: str
209
222
  diff: str
223
+ changed_files: Tuple[str, ...]
210
224
  was_truncated: bool
211
225
  requested_max_bytes: int
212
226
  hard_limit_bytes: int
@@ -337,7 +351,7 @@ def parse_structured_claude_verdict(data: Any) -> ClaudeVerdict:
337
351
 
338
352
  p_counts[int(severity[1])] += 1
339
353
  if len(rendered_findings) < MAX_RENDERED_FINDINGS:
340
- rendered_findings.append(raw_finding)
354
+ rendered_findings.append(dict(raw_finding))
341
355
 
342
356
  blocker_count = p_counts[0] + p_counts[1] + p_counts[2]
343
357
  if blocker_count > 0:
@@ -362,9 +376,162 @@ def parse_structured_claude_verdict(data: Any) -> ClaudeVerdict:
362
376
  p2_count=p_counts[2],
363
377
  p3_count=p_counts[3],
364
378
  mismatch_note=mismatch_note,
379
+ summary=summary,
380
+ findings=tuple(dict(finding) for finding in raw_findings),
365
381
  )
366
382
 
367
383
 
384
+ def _placeholder_equal(value: object) -> bool:
385
+ return str(value or "").strip().lower() in SCHEMA_PLACEHOLDER_TEXT
386
+
387
+
388
+ def _normalize_diff_path(value: object) -> str:
389
+ path = str(value or "").strip().replace("\\", "/")
390
+ while path.startswith("./"):
391
+ path = path[2:]
392
+ if path.startswith(("a/", "b/")):
393
+ path = path[2:]
394
+ return path
395
+
396
+
397
+ def _diff_file_matches(file_path: str, allowed_files: set[str]) -> bool:
398
+ if file_path in allowed_files:
399
+ return True
400
+ suffix_matches = [
401
+ path
402
+ for path in allowed_files
403
+ if path.endswith(f"/{file_path}") or file_path.endswith(f"/{path}")
404
+ ]
405
+ return len(suffix_matches) == 1
406
+
407
+
408
+ def _claude_verdict_guardrail_reason(
409
+ parsed: ClaudeVerdict,
410
+ changed_files: Tuple[str, ...],
411
+ ) -> Optional[str]:
412
+ if parsed.verdict == "UNKNOWN":
413
+ return None
414
+
415
+ if _placeholder_equal(parsed.summary):
416
+ return "structured verdict summary matched a schema placeholder value"
417
+
418
+ allowed_files = {_normalize_diff_path(path) for path in changed_files}
419
+ allowed_files.discard("")
420
+ for idx, finding in enumerate(parsed.findings):
421
+ where = f"findings[{idx}]"
422
+ if _placeholder_equal(finding.get("title")):
423
+ return f"{where}.title matched a schema placeholder value"
424
+ if _placeholder_equal(finding.get("detail")):
425
+ return f"{where}.detail matched a schema placeholder value"
426
+
427
+ file_path = _normalize_diff_path(finding.get("file"))
428
+ severity = str(finding.get("severity") or "")
429
+ if (
430
+ severity in BLOCKER_SEVERITIES
431
+ and (not file_path or not _diff_file_matches(file_path, allowed_files))
432
+ ):
433
+ return f"{where}.file {file_path!r} is not present in the PR diff"
434
+
435
+ if (
436
+ parsed.verdict == "BLOCKED"
437
+ and len(parsed.prose.strip()) < MIN_BLOCKED_VERDICT_PROSE_CHARS
438
+ ):
439
+ return "structured blocked verdict body is too short to be credible"
440
+
441
+ return None
442
+
443
+
444
+ def _apply_claude_verdict_guardrails(
445
+ parsed: ClaudeVerdict,
446
+ changed_files: Tuple[str, ...],
447
+ ) -> Tuple[ClaudeVerdict, Optional[str]]:
448
+ reason = _claude_verdict_guardrail_reason(parsed, changed_files)
449
+ if reason is None:
450
+ return parsed, None
451
+ return _unknown_structured_verdict(reason), reason
452
+
453
+
454
+ def _fixture_structured_quarantine_reason(parsed: ClaudeVerdict) -> Optional[str]:
455
+ if _is_fixture_structured_verdict(parsed.summary, list(parsed.findings)):
456
+ return "fixture-shaped structured verdict rejected by guardrail"
457
+ return None
458
+
459
+
460
+ def _quarantine_is_test_only(reason: Optional[str]) -> bool:
461
+ return reason == "PYTEST_CURRENT_TEST is set"
462
+
463
+
464
+ def _comment_with_runtime_quarantine_note(comment_body: str, reason: str) -> str:
465
+ note = (
466
+ "\nRuntime quarantine: local verdict artifact was quarantined because "
467
+ f"{reason}. Posting UNKNOWN so the lane requeues visibly.\n"
468
+ )
469
+ marker = f"\n\n{STALE_TRAILER}"
470
+ if marker in comment_body:
471
+ body = comment_body.replace(marker, note + "\n" + STALE_TRAILER, 1)
472
+ else:
473
+ body = comment_body.rstrip() + note + "\n" + STALE_TRAILER + "\n"
474
+ return limit_comment_body(body, STALE_TRAILER, provider_name="Claude")
475
+
476
+
477
+ def _write_claude_raw_output_sidecar(
478
+ artifact_path: Optional[Path],
479
+ attempts: List[Dict[str, Any]],
480
+ ) -> None:
481
+ if artifact_path is None:
482
+ return
483
+ sidecar_path = artifact_path.with_suffix(".claude-raw-output.json")
484
+ payload = {"schema": CLAUDE_RAW_OUTPUT_SCHEMA, "attempts": attempts}
485
+ try:
486
+ text = json.dumps(payload, indent=2, sort_keys=True) + "\n"
487
+ sidecar_path.write_text(text, encoding="utf-8")
488
+ except OSError as exc:
489
+ print(
490
+ f"warning: failed to write Claude raw output sidecar {sidecar_path}: {exc}",
491
+ file=sys.stderr,
492
+ )
493
+
494
+
495
+ def _guardrail_retry_prompt(
496
+ prompt: str,
497
+ reason: str,
498
+ changed_files: Tuple[str, ...],
499
+ ) -> str:
500
+ files = "\n".join(f"- {path}" for path in changed_files[:50])
501
+ if len(changed_files) > 50:
502
+ files += f"\n- ... {len(changed_files) - 50} additional file(s) omitted"
503
+ return (
504
+ f"{prompt.rstrip()}\n\n"
505
+ "Trusted wrapper retry instruction:\n"
506
+ f"The previous structured verdict was rejected: {reason}. "
507
+ "Return a fresh JSON verdict only. Do not use placeholder values such "
508
+ "as 'test', 'example', or 'placeholder'. P0/P1/P2 findings must cite a "
509
+ "file from the PR diff, using the repository-relative path or a unique "
510
+ "suffix. PR diff files:\n"
511
+ f"{files}\n"
512
+ )
513
+
514
+
515
+ def _record_posted_comment_url(
516
+ artifact_path: Optional[Path],
517
+ posted_comment_url: Optional[str],
518
+ ) -> None:
519
+ if artifact_path is None or not posted_comment_url:
520
+ return
521
+ try:
522
+ payload = json.loads(artifact_path.read_text(encoding="utf-8"))
523
+ if not isinstance(payload, dict):
524
+ return
525
+ payload["posted_comment_url"] = posted_comment_url
526
+ text = json.dumps(payload, indent=2, sort_keys=True) + "\n"
527
+ artifact_path.write_text(text, encoding="utf-8")
528
+ except (OSError, json.JSONDecodeError) as exc:
529
+ print(
530
+ f"warning: failed to update audit verdict artifact {artifact_path}: {exc}",
531
+ file=sys.stderr,
532
+ )
533
+
534
+
368
535
  def _claude_env() -> Dict[str, str]:
369
536
  preserve_auth_overrides = env_flag(os.environ.get("CODE_MOWER_CLAUDE_KEEP_AUTH_ENV"))
370
537
  env, _removed = clean_claude_cli_env(
@@ -505,6 +672,13 @@ def _build_diff_context(
505
672
  raise FetchedHeadMismatch(expected_head_sha, fetched_head_ref)
506
673
  diff_range = f"{fetched_base_ref}...{fetched_head_ref}"
507
674
  stat = _run_git(local_repo, ["diff", "--stat", "--find-renames", diff_range])
675
+ changed_files_text = _run_git(
676
+ local_repo,
677
+ ["-c", "core.quotePath=false", "diff", "--name-only", "--find-renames", diff_range],
678
+ )
679
+ changed_files = tuple(
680
+ line.strip() for line in changed_files_text.splitlines() if line.strip()
681
+ )
508
682
  included_diff, full_diff_bytes, was_truncated = _run_git_limited(
509
683
  local_repo,
510
684
  ["diff", "--find-renames", "--unified=80", diff_range],
@@ -518,6 +692,7 @@ def _build_diff_context(
518
692
  return DiffContext(
519
693
  stat=stat,
520
694
  diff=included_diff,
695
+ changed_files=changed_files,
521
696
  was_truncated=was_truncated,
522
697
  requested_max_bytes=max_diff_bytes,
523
698
  hard_limit_bytes=hard_limit,
@@ -850,6 +1025,9 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
850
1025
  parsed=parsed,
851
1026
  )
852
1027
  if not config.dry_run:
1028
+ quarantine_reason = _audit_runtime_quarantine_reason(
1029
+ comment_body=comment_body,
1030
+ )
853
1031
  artifact_path = write_audit_verdict_artifact(
854
1032
  lane_id="claude-audit",
855
1033
  repo=repo,
@@ -859,6 +1037,7 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
859
1037
  verdict=result.verdict,
860
1038
  trailer=result.trailer,
861
1039
  comment_body=comment_body,
1040
+ quarantine_reason=quarantine_reason,
862
1041
  )
863
1042
  result.verdict_artifact_path = artifact_path
864
1043
  if artifact_path is not None:
@@ -867,13 +1046,35 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
867
1046
  file=sys.stderr,
868
1047
  flush=True,
869
1048
  )
870
- posted = post_pr_comment(repo, pr_number, comment_body, token=config.github_token)
871
- result.posted_comment_url = posted.get("html_url")
872
- print(
873
- f"posted {repo}#{pr_number} verdict=STALE "
874
- f"url={result.posted_comment_url}",
875
- file=sys.stderr,
876
- )
1049
+ if quarantine_reason:
1050
+ print(
1051
+ " runtime guard quarantined verdict artifact: "
1052
+ f"{quarantine_reason}",
1053
+ file=sys.stderr,
1054
+ flush=True,
1055
+ )
1056
+ if quarantine_reason and _quarantine_is_test_only(quarantine_reason):
1057
+ print(" skipped GitHub post for pytest-only quarantine", file=sys.stderr, flush=True)
1058
+ else:
1059
+ if quarantine_reason:
1060
+ result.verdict = "UNKNOWN"
1061
+ result.trailer = STALE_TRAILER
1062
+ comment_body = _comment_with_runtime_quarantine_note(
1063
+ comment_body,
1064
+ quarantine_reason,
1065
+ )
1066
+ result.comment_body = comment_body
1067
+ posted = post_pr_comment(repo, pr_number, comment_body, token=config.github_token)
1068
+ result.posted_comment_url = posted.get("html_url")
1069
+ _record_posted_comment_url(
1070
+ result.verdict_artifact_path,
1071
+ result.posted_comment_url,
1072
+ )
1073
+ print(
1074
+ f"posted {repo}#{pr_number} verdict=STALE "
1075
+ f"url={result.posted_comment_url}",
1076
+ file=sys.stderr,
1077
+ )
877
1078
  config.progress.emit(
878
1079
  "audit",
879
1080
  status="finish",
@@ -927,8 +1128,48 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
927
1128
  ),
928
1129
  )
929
1130
 
1131
+ raw_output_attempts: List[Dict[str, Any]] = []
1132
+ parsed = _unknown_structured_verdict("Claude audit did not run")
1133
+ claude_stdout = ""
1134
+ claude_stderr = ""
1135
+ attempt_prompt = prompt
1136
+ final_fixture_reason: Optional[str] = None
930
1137
  t0 = time.time()
931
- parsed, claude_stdout, claude_stderr = run_claude_audit(config, prompt)
1138
+ for attempt in range(1, MAX_CLAUDE_AUDIT_ATTEMPTS + 1):
1139
+ parsed_candidate, attempt_stdout, attempt_stderr = run_claude_audit(
1140
+ config,
1141
+ attempt_prompt,
1142
+ )
1143
+ claude_stdout = attempt_stdout
1144
+ claude_stderr = attempt_stderr
1145
+ parsed, guardrail_reason = _apply_claude_verdict_guardrails(
1146
+ parsed_candidate,
1147
+ diff_context.changed_files,
1148
+ )
1149
+ final_fixture_reason = _fixture_structured_quarantine_reason(parsed_candidate)
1150
+ raw_output_attempts.append(
1151
+ {
1152
+ "attempt": attempt,
1153
+ "stdout": attempt_stdout,
1154
+ "stderr": attempt_stderr,
1155
+ "verdict": parsed_candidate.verdict,
1156
+ "guardrail_rejection": guardrail_reason,
1157
+ }
1158
+ )
1159
+ if guardrail_reason and attempt < MAX_CLAUDE_AUDIT_ATTEMPTS:
1160
+ print(
1161
+ " structured-verdict guardrail rejected Claude output: "
1162
+ f"{guardrail_reason}; retrying once",
1163
+ file=sys.stderr,
1164
+ flush=True,
1165
+ )
1166
+ attempt_prompt = _guardrail_retry_prompt(
1167
+ prompt,
1168
+ guardrail_reason,
1169
+ diff_context.changed_files,
1170
+ )
1171
+ continue
1172
+ break
932
1173
  dt = time.time() - t0
933
1174
  print(f" claude audit completed in {dt:.0f}s", file=sys.stderr, flush=True)
934
1175
  if parsed.mismatch_note:
@@ -984,6 +1225,10 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
984
1225
  )
985
1226
 
986
1227
  if not config.dry_run:
1228
+ quarantine_reason = _audit_runtime_quarantine_reason(
1229
+ comment_body=comment_body,
1230
+ fixture_reason=final_fixture_reason if parsed.verdict == "UNKNOWN" else None,
1231
+ )
987
1232
  artifact_path = write_audit_verdict_artifact(
988
1233
  lane_id="claude-audit",
989
1234
  repo=repo,
@@ -993,6 +1238,7 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
993
1238
  verdict=result_verdict,
994
1239
  trailer=trailer,
995
1240
  comment_body=comment_body,
1241
+ quarantine_reason=quarantine_reason,
996
1242
  )
997
1243
  result.verdict_artifact_path = artifact_path
998
1244
  if artifact_path is not None:
@@ -1001,17 +1247,40 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
1001
1247
  file=sys.stderr,
1002
1248
  flush=True,
1003
1249
  )
1004
- posted = post_pr_comment(repo, pr_number, comment_body, token=config.github_token)
1005
- result.posted_comment_url = posted.get("html_url")
1006
- print(
1007
- f"posted {repo}#{pr_number} verdict={result_verdict} "
1008
- f"url={result.posted_comment_url}",
1009
- file=sys.stderr,
1010
- )
1250
+ _write_claude_raw_output_sidecar(artifact_path, raw_output_attempts)
1251
+ if quarantine_reason:
1252
+ print(
1253
+ " runtime guard quarantined verdict artifact: "
1254
+ f"{quarantine_reason}",
1255
+ file=sys.stderr,
1256
+ flush=True,
1257
+ )
1258
+ if quarantine_reason and _quarantine_is_test_only(quarantine_reason):
1259
+ print(" skipped GitHub post for pytest-only quarantine", file=sys.stderr, flush=True)
1260
+ else:
1261
+ if quarantine_reason:
1262
+ result.verdict = "UNKNOWN"
1263
+ result.trailer = STALE_TRAILER
1264
+ comment_body = _comment_with_runtime_quarantine_note(
1265
+ comment_body,
1266
+ quarantine_reason,
1267
+ )
1268
+ result.comment_body = comment_body
1269
+ posted = post_pr_comment(repo, pr_number, comment_body, token=config.github_token)
1270
+ result.posted_comment_url = posted.get("html_url")
1271
+ _record_posted_comment_url(
1272
+ result.verdict_artifact_path,
1273
+ result.posted_comment_url,
1274
+ )
1275
+ print(
1276
+ f"posted {repo}#{pr_number} verdict={result_verdict} "
1277
+ f"url={result.posted_comment_url}",
1278
+ file=sys.stderr,
1279
+ )
1011
1280
  config.progress.emit(
1012
1281
  "audit",
1013
1282
  status="finish",
1014
- detail=f"{repo}#{pr_number} verdict={result_verdict}",
1283
+ detail=f"{repo}#{pr_number} verdict={result.verdict}",
1015
1284
  )
1016
1285
  return result
1017
1286
 
@@ -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.0b44`, or run source checkouts with "
18
18
  "`PYTHONPATH=src python -m code_mower.cli`."
19
19
  )
20
20
 
@@ -18,12 +18,16 @@ if __package__ in {None, ""}:
18
18
  sys.path.insert(0, str(module_dir.parent))
19
19
  if module_dir.name == "code_mower": # pragma: no cover - extracted direct CLI.
20
20
  from code_mower import audit_handoff_log
21
+ from code_mower.provider_runners import is_fixture_verdict_artifact
21
22
  else:
22
23
  from tools import audit_handoff_log
24
+ from tools.provider_runners import is_fixture_verdict_artifact
23
25
  elif __package__ == "tools":
24
26
  from tools import audit_handoff_log
27
+ from tools.provider_runners import is_fixture_verdict_artifact
25
28
  else: # pragma: no cover - exercised after package extraction.
26
29
  from . import audit_handoff_log
30
+ from .provider_runners import is_fixture_verdict_artifact
27
31
 
28
32
 
29
33
  PASS_VERDICTS = {"completed", "done", "pass", "passed", "success", "succeeded"}
@@ -31,7 +35,6 @@ BLOCKED_VERDICTS = {"block", "blocked", "fail", "failed", "failure"}
31
35
  VERDICT_ARTIFACT_SCHEMA = "code_mower.auditVerdictArtifact.v1"
32
36
  BENCHMARK_EVENT_SCHEMA = "code_mower.benchmarkEvent.v1"
33
37
  VERDICT_ARTIFACT_DIR_ENV = "CODE_MOWER_VERDICT_ARTIFACT_DIR"
34
- DEFAULT_VERDICT_ARTIFACT_DIR = Path.home() / ".cache" / "code-mower-audits" / "verdicts"
35
38
  SEVERITY_RE = re.compile(r"\b(P[0-3])=(\d+)\b")
36
39
  AUDIT_TRAILER_RE = re.compile(
37
40
  r"<!--\s*(?P<prefix>[A-Z0-9_]+_AUDIT_STATE)\s*:\s*(?P<label>[a-z0-9_.-]+)\s*-->",
@@ -43,7 +46,10 @@ def default_verdict_artifact_dir() -> Path:
43
46
  configured = os.environ.get(VERDICT_ARTIFACT_DIR_ENV, "").strip()
44
47
  if configured:
45
48
  return Path(configured).expanduser()
46
- return DEFAULT_VERDICT_ARTIFACT_DIR
49
+ cache_home = os.environ.get("XDG_CACHE_HOME", "").strip()
50
+ if cache_home:
51
+ return Path(cache_home).expanduser() / "code-mower-audits" / "verdicts"
52
+ return Path.home() / ".cache" / "code-mower-audits" / "verdicts"
47
53
 
48
54
 
49
55
  def _normalize_repo_slug(value: str) -> str:
@@ -249,6 +255,8 @@ def export_reviewer_run_events_from_verdicts(
249
255
  raise ValueError(f"{path}: verdict artifact must be a JSON object")
250
256
  if payload.get("schema") != VERDICT_ARTIFACT_SCHEMA:
251
257
  continue
258
+ if is_fixture_verdict_artifact(payload):
259
+ continue
252
260
  artifact_repo = _normalize_repo_slug(
253
261
  str(payload.get("repo") or _repo_slug_from_artifact_path(path))
254
262
  )