code-mower 0.5.0b47__tar.gz → 0.5.0b49__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 (302) hide show
  1. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/PKG-INFO +6 -6
  2. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/README.md +5 -5
  3. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/pyproject.toml +1 -1
  4. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/__init__.py +1 -1
  5. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/audit_labeler_lib.py +190 -0
  6. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/__init__.py +2 -0
  7. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/effect_report.py +10 -2
  8. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/evidence_report.py +5 -0
  9. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/policy.py +2 -0
  10. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/run_status.py +4 -1
  11. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/value_report.py +7 -5
  12. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/claude_audit_pr.py +23 -2
  13. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/cli.py +1 -1
  14. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/code_mower_calibration.py +5 -0
  15. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/codex_audit_pr.py +25 -2
  16. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/gate_health.py +2 -1
  17. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/reviewer_metrics.py +3 -0
  18. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/reviewer-value-report.example.md +3 -3
  19. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/workflows/builder-provenance.yml.j2 +1 -1
  20. {code_mower-0.5.0b47 → code_mower-0.5.0b49/src/code_mower}/templates/workflows/code-mower-gate.yml.j2 +310 -35
  21. {code_mower-0.5.0b47 → code_mower-0.5.0b49/src/code_mower}/templates/workflows/self-hosted-local-audit.yml.j2 +43 -56
  22. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/trailer_comment_labeler.py +105 -0
  23. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower.egg-info/PKG-INFO +6 -6
  24. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/reviewer-value-report.example.md +3 -3
  25. {code_mower-0.5.0b47/src/code_mower → code_mower-0.5.0b49}/templates/workflows/code-mower-gate.yml.j2 +310 -35
  26. {code_mower-0.5.0b47/src/code_mower → code_mower-0.5.0b49}/templates/workflows/self-hosted-local-audit.yml.j2 +43 -56
  27. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_calibration_effect_report.py +33 -0
  28. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_calibration_truth.py +41 -0
  29. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_claude_audit_pr.py +18 -0
  30. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_codex_audit_pr.py +18 -0
  31. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_gate_health.py +16 -0
  32. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_release_hygiene.py +315 -38
  33. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_reviewer_metrics_core.py +4 -0
  34. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_trailer_comment_labeler.py +119 -1
  35. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/CODE_OF_CONDUCT.md +0 -0
  36. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/LICENSE +0 -0
  37. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/MANIFEST.in +0 -0
  38. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/NOTICE +0 -0
  39. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/SUPPORT.md +0 -0
  40. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/requirements/requirements.txt +0 -0
  41. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/setup.cfg +0 -0
  42. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/adapters/__init__.py +0 -0
  43. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/adapters/_base.py +0 -0
  44. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/adapters/cursor_bugbot.py +0 -0
  45. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/adapters/gitar.py +0 -0
  46. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/adapters/greptile.py +0 -0
  47. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/adapters/qodo.py +0 -0
  48. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/antigravity_cli_audit_pr.py +0 -0
  49. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/audit_handoff_log.py +0 -0
  50. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/audit_progress.py +0 -0
  51. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/blind_review_artifacts.py +0 -0
  52. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/blind_review_coordinator.py +0 -0
  53. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/bootstrap.py +0 -0
  54. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/builder_experiment.py +0 -0
  55. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/builder_runs.py +0 -0
  56. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/arms.py +0 -0
  57. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/auto_discovery.py +0 -0
  58. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/commands.py +0 -0
  59. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/context_inputs.py +0 -0
  60. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/corpus.py +0 -0
  61. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/evidence.py +0 -0
  62. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/identity.py +0 -0
  63. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/metrics.py +0 -0
  64. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/overlap.py +0 -0
  65. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/planning.py +0 -0
  66. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/results.py +0 -0
  67. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/run_results.py +0 -0
  68. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/runner.py +0 -0
  69. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/calibration/truth.py +0 -0
  70. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/checks.py +0 -0
  71. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/claude_cli_bounce.py +0 -0
  72. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/claude_cli_environment.py +0 -0
  73. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/clear_stale.py +0 -0
  74. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/cloud.py +0 -0
  75. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/cloud_client/__init__.py +0 -0
  76. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/cloud_client/bundle.py +0 -0
  77. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/cloud_client/doctor.py +0 -0
  78. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/cloud_client/dogfood.py +0 -0
  79. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/cloud_client/endpoints.py +0 -0
  80. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/cloud_client/errors.py +0 -0
  81. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/cloud_client/events.py +0 -0
  82. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/cloud_client/export.py +0 -0
  83. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/cloud_client/git_metadata.py +0 -0
  84. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/cloud_client/manifest.py +0 -0
  85. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/cloud_client/operations.py +0 -0
  86. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/cloud_client/reports.py +0 -0
  87. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/cloud_client/setup.py +0 -0
  88. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/cloud_client/upload.py +0 -0
  89. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/code_mower_context_packs.py +0 -0
  90. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/code_mower_merge.py +0 -0
  91. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/code_mower_telemetry.py +0 -0
  92. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/coderabbit_cli_audit_pr.py +0 -0
  93. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/codex_audit_env_preflight.py +0 -0
  94. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/codex_audit_schema_smoke.py +0 -0
  95. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/codex_audit_verdict.schema.json +0 -0
  96. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/config.py +0 -0
  97. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor.py +0 -0
  98. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/__init__.py +0 -0
  99. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/cloud.py +0 -0
  100. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/common.py +0 -0
  101. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/github.py +0 -0
  102. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/github_actions.py +0 -0
  103. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/github_actions_cost.py +0 -0
  104. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/github_actions_cost_summary.py +0 -0
  105. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/github_actions_failure_annotations.py +0 -0
  106. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/github_actions_failure_models.py +0 -0
  107. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/github_actions_failure_scan.py +0 -0
  108. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/github_actions_failure_selection.py +0 -0
  109. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/github_actions_failures.py +0 -0
  110. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/github_actions_permissions.py +0 -0
  111. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/github_api.py +0 -0
  112. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/github_branch.py +0 -0
  113. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/github_config.py +0 -0
  114. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/github_provider.py +0 -0
  115. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/github_repo.py +0 -0
  116. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/groups.py +0 -0
  117. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/models.py +0 -0
  118. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/output.py +0 -0
  119. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/presets.py +0 -0
  120. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/privacy.py +0 -0
  121. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/provider_api_model.py +0 -0
  122. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/provider_api_model_openai.py +0 -0
  123. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/provider_api_model_profiles.py +0 -0
  124. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/provider_env.py +0 -0
  125. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/provider_env_required.py +0 -0
  126. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/provider_env_tokens.py +0 -0
  127. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/provider_local_cli.py +0 -0
  128. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/provider_local_cli_commands.py +0 -0
  129. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/provider_local_cli_probe_config.py +0 -0
  130. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/provider_probe.py +0 -0
  131. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/provider_probe_auth.py +0 -0
  132. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/provider_probe_evaluation.py +0 -0
  133. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/provider_probe_json.py +0 -0
  134. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/provider_probe_remediation.py +0 -0
  135. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/provider_review_hygiene.py +0 -0
  136. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/providers.py +0 -0
  137. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/registry.py +0 -0
  138. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/runner.py +0 -0
  139. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/runtime.py +0 -0
  140. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/doctor_checks/runtime_github_auth.py +0 -0
  141. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/gemini_cli_audit_pr.py +0 -0
  142. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/grok_build_audit_pr.py +0 -0
  143. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/hermes_cli_audit_pr.py +0 -0
  144. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/init.py +0 -0
  145. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/lane_configs/__init__.py +0 -0
  146. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/lane_configs/aider.py +0 -0
  147. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/lane_configs/antigravity_cli.py +0 -0
  148. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/lane_configs/claude.py +0 -0
  149. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/lane_configs/codex.py +0 -0
  150. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/lane_configs/devin.py +0 -0
  151. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/lane_configs/gemini_cli.py +0 -0
  152. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/lane_configs/grok_build.py +0 -0
  153. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/lane_configs/hermes_cli.py +0 -0
  154. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/lane_configs/local_llm.py +0 -0
  155. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/local_llm_audit_pr.py +0 -0
  156. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/local_llm_bakeoff.py +0 -0
  157. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/local_llm_calibration.py +0 -0
  158. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/local_llm_profiles.py +0 -0
  159. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/migration.py +0 -0
  160. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/migration_install.py +0 -0
  161. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/migration_mirror.py +0 -0
  162. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/migration_readiness.py +0 -0
  163. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/migration_rehearsal.py +0 -0
  164. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/next_steps.py +0 -0
  165. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/package.py +0 -0
  166. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/package_content.py +0 -0
  167. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/package_manifest.py +0 -0
  168. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/package_paths.py +0 -0
  169. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/package_rendering.py +0 -0
  170. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/package_static.py +0 -0
  171. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/plan_context.py +0 -0
  172. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/prompts.py +0 -0
  173. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/provider_registry.py +0 -0
  174. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/provider_runners/__init__.py +0 -0
  175. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/provider_runners/comments.py +0 -0
  176. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/provider_runners/git.py +0 -0
  177. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/provider_runners/github_auth.py +0 -0
  178. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/provider_runners/github_pr.py +0 -0
  179. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/provider_runners/pr_worktree.py +0 -0
  180. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/provider_runners/process.py +0 -0
  181. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/provider_runners/repo_paths.py +0 -0
  182. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/provider_runners/text_schema.py +0 -0
  183. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/provider_runners/verdict_artifacts.py +0 -0
  184. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/provider_runners/workspace.py +0 -0
  185. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/providers/__init__.py +0 -0
  186. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/providers/local_cli.py +0 -0
  187. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/providers/provenance.py +0 -0
  188. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/release_readiness.py +0 -0
  189. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/reviewer_spend.py +0 -0
  190. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/saas_reviewer_labeler.py +0 -0
  191. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/secrets.py +0 -0
  192. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/builder-experiment.example.json +0 -0
  193. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/calibration-corpus.example.json +0 -0
  194. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/calibration-corpus.json +0 -0
  195. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/code-mower.example.yml +0 -0
  196. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/context-packs.example.json +0 -0
  197. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/lane_prompts/base-audit.md +0 -0
  198. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/lane_prompts/calibration-policy.md +0 -0
  199. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/lane_prompts/context-driven-quality.md +0 -0
  200. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/lane_prompts/docs-design.md +0 -0
  201. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/lane_prompts/generic-programming.md +0 -0
  202. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/lane_prompts/operability.md +0 -0
  203. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/lane_prompts/package-runtime.md +0 -0
  204. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/lane_prompts/plan-conformance.md +0 -0
  205. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/lane_prompts/security-threat-model.md +0 -0
  206. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/product-support/code_mower +0 -0
  207. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/product-support/code_mower_standalone_pin.env +0 -0
  208. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/product-support/code_mower_standalone_shadow.sh +0 -0
  209. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/product-support/run_claude_audit_pr.sh +0 -0
  210. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/product-support/run_codex_audit_pr.sh +0 -0
  211. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/product-support/safe_gh_comment.py +0 -0
  212. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/product-support/status_report.py +0 -0
  213. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/providers.yml +0 -0
  214. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/reviewer-spend.example.json +0 -0
  215. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/workflows/audit-label-cleanup.yml.j2 +0 -0
  216. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/workflows/code-mower-gate-health.yml.j2 +0 -0
  217. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/workflows/hosted-bridge.yml.j2 +0 -0
  218. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/workflows/local-cli-audit.yml.j2 +0 -0
  219. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/workflows/needs-owner-notify.yml.j2 +0 -0
  220. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/workflows/private-standalone-shadow.yml.j2 +0 -0
  221. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/workflows/review-clear-stale.yml.j2 +0 -0
  222. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/workflows/saas-reviewer-labeler.yml.j2 +0 -0
  223. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/workflows/trailer-comment-labeler.yml.j2 +0 -0
  224. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/templates/workflows/weekly-status.yml.j2 +0 -0
  225. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/versioning.py +0 -0
  226. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower/work_orders.py +0 -0
  227. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower.egg-info/SOURCES.txt +0 -0
  228. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower.egg-info/dependency_links.txt +0 -0
  229. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower.egg-info/entry_points.txt +0 -0
  230. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower.egg-info/requires.txt +0 -0
  231. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/src/code_mower.egg-info/top_level.txt +0 -0
  232. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/builder-experiment.example.json +0 -0
  233. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/calibration-corpus.example.json +0 -0
  234. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/calibration-corpus.json +0 -0
  235. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/code-mower.yml.j2 +0 -0
  236. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/context-packs.example.json +0 -0
  237. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/cursor/BUGBOT.md +0 -0
  238. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/providers/acp_bridge.yml +0 -0
  239. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/providers/aider.yml +0 -0
  240. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/providers/antigravity_cli.yml +0 -0
  241. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/providers/claude_audit.yml +0 -0
  242. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/providers/claude_review.yml +0 -0
  243. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/providers/coderabbit_cli.yml +0 -0
  244. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/providers/codex.yml +0 -0
  245. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/providers/cursor_bugbot.yml +0 -0
  246. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/providers/devin.yml +0 -0
  247. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/providers/gemini_cli.yml +0 -0
  248. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/providers/gitar.yml +0 -0
  249. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/providers/greptile.yml +0 -0
  250. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/providers/hermes_cli.yml +0 -0
  251. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/providers/local_llm.yml +0 -0
  252. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/providers/qodo.yml +0 -0
  253. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/providers.yml +0 -0
  254. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/reviewer-spend.example.json +0 -0
  255. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/workflows/audit-label-cleanup.yml.j2 +0 -0
  256. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/workflows/blind-review-artifacts-dry-run.yml.j2 +0 -0
  257. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/workflows/builder-provenance.yml.j2 +0 -0
  258. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/workflows/code-mower-gate-health.yml.j2 +0 -0
  259. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/workflows/hosted-bridge.yml.j2 +0 -0
  260. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/workflows/local-cli-audit.yml.j2 +0 -0
  261. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/workflows/private-standalone-shadow.yml.j2 +0 -0
  262. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/workflows/review-clear-stale.yml.j2 +0 -0
  263. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/workflows/saas-reviewer-labeler.yml.j2 +0 -0
  264. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/templates/workflows/trailer-comment-labeler.yml.j2 +0 -0
  265. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_audit_comment_posture.py +0 -0
  266. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_builder_identity.py +0 -0
  267. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_builder_runs.py +0 -0
  268. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_checks.py +0 -0
  269. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_claude_cli_bounce.py +0 -0
  270. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_clear_stale.py +0 -0
  271. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_cloud_client.py +0 -0
  272. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_cloud_git_metadata.py +0 -0
  273. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_cloud_manifest.py +0 -0
  274. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_cloud_reports.py +0 -0
  275. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_coderabbit_cli_audit_pr.py +0 -0
  276. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_codex_audit_env_preflight.py +0 -0
  277. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_doctor_actions_cost_summary.py +0 -0
  278. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_doctor_actions_failure_selection.py +0 -0
  279. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_doctor_boundaries.py +0 -0
  280. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_doctor_github_checks.py +0 -0
  281. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_doctor_output.py +0 -0
  282. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_doctor_privacy.py +0 -0
  283. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_doctor_provider_env_required.py +0 -0
  284. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_doctor_provider_env_tokens.py +0 -0
  285. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_doctor_provider_probe.py +0 -0
  286. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_doctor_provider_review_hygiene.py +0 -0
  287. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_doctor_registry.py +0 -0
  288. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_doctor_runtime.py +0 -0
  289. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_local_llm_github_helpers.py +0 -0
  290. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_plan_context.py +0 -0
  291. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_prompts.py +0 -0
  292. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_provider_local_cli.py +0 -0
  293. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_provider_runners_comments.py +0 -0
  294. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_provider_runners_git.py +0 -0
  295. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_provider_runners_github_pr.py +0 -0
  296. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_provider_runners_pr_worktree.py +0 -0
  297. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_provider_runners_process.py +0 -0
  298. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_provider_runners_repo_paths.py +0 -0
  299. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_provider_runners_text_schema.py +0 -0
  300. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_provider_runners_workspace.py +0 -0
  301. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/tests/test_reviewer_spend.py +0 -0
  302. {code_mower-0.5.0b47 → code_mower-0.5.0b49}/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.0b47
3
+ Version: 0.5.0b49
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.0b47
106
+ pipx install --python python3.12 code-mower==0.5.0b49
107
107
  code-mower --version
108
108
  ```
109
109
 
110
- `0.5.0b47` is a beta release. Until Code Mower publishes a stable `1.0`
110
+ `0.5.0b49` 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.0b47`.
158
+ the latest 10/10 public-package readiness proof for `code-mower==0.5.0b49`.
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.47` from
313
+ The current public beta is `v0.5.0-beta.49` from
314
314
  [codemower-ai/code-mower](https://github.com/codemower-ai/code-mower), published
315
- as `code-mower==0.5.0b47` on [PyPI](https://pypi.org/project/code-mower/).
315
+ as `code-mower==0.5.0b49` 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.0b47
91
+ pipx install --python python3.12 code-mower==0.5.0b49
92
92
  code-mower --version
93
93
  ```
94
94
 
95
- `0.5.0b47` is a beta release. Until Code Mower publishes a stable `1.0`
95
+ `0.5.0b49` 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.0b47`.
143
+ the latest 10/10 public-package readiness proof for `code-mower==0.5.0b49`.
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.47` from
298
+ The current public beta is `v0.5.0-beta.49` from
299
299
  [codemower-ai/code-mower](https://github.com/codemower-ai/code-mower), published
300
- as `code-mower==0.5.0b47` on [PyPI](https://pypi.org/project/code-mower/).
300
+ as `code-mower==0.5.0b49` 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.0b47"
7
+ version = "0.5.0b49"
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.0b47"
3
+ __version__ = "0.5.0b49"
@@ -30,6 +30,10 @@ ACTIONS_RUN_MARKER_RE = re.compile(
30
30
  r"(?:\s+body_sha256=([0-9a-f]{64}))?\s*-->"
31
31
  )
32
32
  TRUSTED_ACTIONS_AUDIT_EVENTS = frozenset({"pull_request_target"})
33
+ AUDIT_RUN_NON_TERMINAL_STATUSES = frozenset(
34
+ {"queued", "requested", "waiting", "pending", "in_progress"}
35
+ )
36
+ HEAD_SHA_LINE_RE = re.compile(r"Head SHA:\s*`?([0-9a-fA-F]{7,40})`?", re.IGNORECASE)
33
37
 
34
38
 
35
39
  @dataclass(frozen=True)
@@ -344,6 +348,192 @@ def workflow_path_matches(path: str, trusted_workflows: Sequence[str]) -> bool:
344
348
  return any(path == item or path.endswith("/" + item) for item in trusted_workflows)
345
349
 
346
350
 
351
+ def flatten_paginated_items(payload: Any) -> list[dict[str, Any]]:
352
+ items: list[dict[str, Any]] = []
353
+ if isinstance(payload, list):
354
+ for page in payload:
355
+ if isinstance(page, list):
356
+ items.extend(item for item in page if isinstance(item, dict))
357
+ elif isinstance(page, dict):
358
+ items.append(page)
359
+ return items
360
+
361
+
362
+ def audit_comment_head_sha(body: str) -> str:
363
+ match = HEAD_SHA_LINE_RE.search(body)
364
+ return match.group(1) if match else ""
365
+
366
+
367
+ def terminal_audit_trailer_verdict(lane: Mapping[str, Any], body: str) -> str:
368
+ candidates: list[tuple[int, str]] = []
369
+ for label_key, verdict in (("done", "done"), ("blocked", "blocked")):
370
+ label = str(lane.get(label_key) or "")
371
+ if not label:
372
+ continue
373
+ index = body.rfind(": " + label + " -->")
374
+ if index >= 0:
375
+ candidates.append((index, verdict))
376
+ if not candidates:
377
+ return ""
378
+ return max(candidates, key=lambda item: item[0])[1]
379
+
380
+
381
+ def current_audit_trailer_verdict(
382
+ lane: Mapping[str, Any],
383
+ body: str,
384
+ *,
385
+ head_sha: str,
386
+ ) -> str:
387
+ if "Head SHA: `" + head_sha + "`" not in body:
388
+ return ""
389
+ return terminal_audit_trailer_verdict(lane, body)
390
+
391
+
392
+ def latest_current_audit_verdict(
393
+ lane: Mapping[str, Any],
394
+ comments: Sequence[Mapping[str, Any]],
395
+ *,
396
+ head_sha: str,
397
+ trusted_comment_author: Callable[
398
+ [Mapping[str, Any], str, str, object, str | None],
399
+ bool,
400
+ ],
401
+ ) -> str:
402
+ verdicts: list[tuple[tuple[str, str, int, int], str]] = []
403
+ for index, comment in enumerate(comments):
404
+ author = str(((comment.get("user") or {}).get("login")) or "")
405
+ body = str(comment.get("body") or "")
406
+ if not trusted_comment_author(lane, author, body, comment.get("id"), None):
407
+ continue
408
+ verdict = current_audit_trailer_verdict(lane, body, head_sha=head_sha)
409
+ if verdict:
410
+ try:
411
+ comment_id = int(comment.get("id") or 0)
412
+ except (TypeError, ValueError):
413
+ comment_id = 0
414
+ updated = str(
415
+ comment.get("updated_at")
416
+ or comment.get("updatedAt")
417
+ or comment.get("created_at")
418
+ or comment.get("createdAt")
419
+ or ""
420
+ )
421
+ created = str(comment.get("created_at") or comment.get("createdAt") or "")
422
+ verdicts.append(((updated, created, comment_id, index), verdict))
423
+ if verdicts:
424
+ return max(verdicts, key=lambda item: item[0])[1]
425
+ return ""
426
+
427
+
428
+ def attested_non_current_audit_heads(
429
+ lanes: Sequence[Mapping[str, Any]],
430
+ comments: Sequence[Mapping[str, Any]],
431
+ *,
432
+ head_sha: str,
433
+ trusted_comment_author: Callable[
434
+ [Mapping[str, Any], str, str, object, str | None],
435
+ bool,
436
+ ],
437
+ ) -> list[str]:
438
+ heads: list[str] = []
439
+ for comment in comments:
440
+ author = str(((comment.get("user") or {}).get("login")) or "")
441
+ body = str(comment.get("body") or "")
442
+ reviewed = audit_comment_head_sha(body)
443
+ if not reviewed or sha_matches(reviewed, head_sha):
444
+ continue
445
+ for lane in lanes:
446
+ if not terminal_audit_trailer_verdict(lane, body):
447
+ continue
448
+ if not trusted_comment_author(lane, author, body, comment.get("id"), reviewed):
449
+ continue
450
+ heads.append(reviewed)
451
+ break
452
+ return list(dict.fromkeys(heads))
453
+
454
+
455
+ def audit_run_workflow_path(run: Mapping[str, Any]) -> str:
456
+ return str(run.get("path") or run.get("workflow_path") or run.get("workflowPath") or "")
457
+
458
+
459
+ def audit_run_matches_pr_head(
460
+ run: Mapping[str, Any],
461
+ *,
462
+ head_sha: str,
463
+ pr_number: int | str,
464
+ ) -> bool:
465
+ run_head_sha = str(run.get("head_sha") or "")
466
+ pull_requests = [
467
+ item for item in run.get("pull_requests") or [] if isinstance(item, Mapping)
468
+ ]
469
+ for item in pull_requests:
470
+ if not isinstance(item, Mapping):
471
+ continue
472
+ if str(item.get("number") or "") != str(pr_number):
473
+ continue
474
+ pr_head = str(((item.get("head") or {}).get("sha")) or "")
475
+ if pr_head and sha_matches(pr_head, head_sha):
476
+ return True
477
+ if run_head_sha and sha_matches(run_head_sha, head_sha):
478
+ return True
479
+ if pull_requests:
480
+ return False
481
+ if run_head_sha and sha_matches(run_head_sha, head_sha):
482
+ return True
483
+ return False
484
+
485
+
486
+ def _audit_lane_match_tokens(lane: Mapping[str, Any]) -> set[str]:
487
+ tokens = [
488
+ str(lane.get("id") or ""),
489
+ str(lane.get("author_lane") or ""),
490
+ str(lane.get("display_name") or ""),
491
+ ]
492
+ for label_key in ("done", "blocked"):
493
+ label = str(lane.get(label_key) or "")
494
+ for suffix in ("-audit-done", "-audit-blocked", "-done", "-blocked"):
495
+ if label.endswith(suffix):
496
+ tokens.append(label[: -len(suffix)])
497
+ return {token.strip().lower().replace("_", "-") for token in tokens if token.strip()}
498
+
499
+
500
+ def audit_job_matches_lane(job: Mapping[str, Any], lane: Mapping[str, Any]) -> bool:
501
+ name = str(job.get("name") or "").lower().replace("_", "-")
502
+ return any(token in name for token in _audit_lane_match_tokens(lane))
503
+
504
+
505
+ def audit_run_in_flight_for_lane(
506
+ entry: Mapping[str, Any],
507
+ lane: Mapping[str, Any],
508
+ *,
509
+ head_sha: str,
510
+ pr_number: int | str,
511
+ ) -> bool:
512
+ run = entry.get("run") if isinstance(entry.get("run"), Mapping) else entry
513
+ if not isinstance(run, Mapping):
514
+ return False
515
+ if str(run.get("status") or "") not in AUDIT_RUN_NON_TERMINAL_STATUSES:
516
+ return False
517
+ workflows = parse_csv_set(str(lane.get("github_actions_workflows") or ""))
518
+ if not workflow_path_matches(audit_run_workflow_path(run), workflows):
519
+ return False
520
+ if not audit_run_matches_pr_head(run, head_sha=head_sha, pr_number=pr_number):
521
+ return False
522
+ jobs = entry.get("jobs") if isinstance(entry.get("jobs"), list) else []
523
+ non_terminal_jobs = [
524
+ job
525
+ for job in jobs
526
+ if isinstance(job, Mapping)
527
+ and (
528
+ str(job.get("status") or "") in AUDIT_RUN_NON_TERMINAL_STATUSES
529
+ or (str(job.get("status") or "") != "completed" and job.get("conclusion") is None)
530
+ )
531
+ ]
532
+ if non_terminal_jobs:
533
+ return any(audit_job_matches_lane(job, lane) for job in non_terminal_jobs)
534
+ return bool(entry.get("jobs_fetch_failed") or not jobs)
535
+
536
+
347
537
  def _pr_item_matches_head(
348
538
  item: Mapping[str, Any],
349
539
  *,
@@ -85,6 +85,7 @@ from .run_status import (
85
85
  RUN_STATUS_CATEGORY_ALIASES,
86
86
  RUN_STATUS_INFRA_ERROR,
87
87
  RUN_STATUS_PASS,
88
+ RUN_STATUS_STALE,
88
89
  RUN_STATUS_UNKNOWN,
89
90
  count_normalized_findings,
90
91
  normalize_run_status_category,
@@ -119,6 +120,7 @@ __all__ = [
119
120
  "RUN_STATUS_CATEGORY_ALIASES",
120
121
  "RUN_STATUS_INFRA_ERROR",
121
122
  "RUN_STATUS_PASS",
123
+ "RUN_STATUS_STALE",
122
124
  "RUN_STATUS_UNKNOWN",
123
125
  "SELECTIVE_USEFUL_RATE",
124
126
  "TRUTH_EXPECTATION_ALIASES",
@@ -12,6 +12,7 @@ from .run_status import (
12
12
  RUN_STATUS_BLOCKED,
13
13
  RUN_STATUS_INFRA_ERROR,
14
14
  RUN_STATUS_PASS,
15
+ RUN_STATUS_STALE,
15
16
  normalize_run_status_category,
16
17
  )
17
18
 
@@ -39,6 +40,7 @@ class CellStats:
39
40
  known_blocked_missed_runs: int = 0
40
41
  audit_input_insufficient_runs: int = 0
41
42
  infra_error_runs: int = 0
43
+ stale_runs: int = 0
42
44
  unknown_blocked_runs: int = 0
43
45
  known_clean_runs: int = 0
44
46
  known_clean_pass_runs: int = 0
@@ -94,6 +96,7 @@ class CellStats:
94
96
  "clean_nonblocking_finding_runs": self.clean_nonblocking_finding_runs,
95
97
  "audit_input_insufficient_runs": self.audit_input_insufficient_runs,
96
98
  "infra_error_runs": self.infra_error_runs,
99
+ "stale_runs": self.stale_runs,
97
100
  "unknown_blocked_runs": self.unknown_blocked_runs,
98
101
  "duration_seconds_total": round(self.duration_seconds_total, 3),
99
102
  "seconds_per_run": _round_or_none(self.seconds_per_run),
@@ -209,8 +212,12 @@ def build_effect_report(
209
212
  cell.audit_input_insufficient_runs += 1
210
213
  elif status_category == RUN_STATUS_INFRA_ERROR:
211
214
  cell.infra_error_runs += 1
215
+ elif status_category == RUN_STATUS_STALE:
216
+ cell.stale_runs += 1
212
217
  else:
213
218
  cell.unknown_blocked_runs += 1
219
+ elif status_category == RUN_STATUS_STALE:
220
+ cell.stale_runs += 1
214
221
  if known_clean:
215
222
  cell.known_clean_runs += 1
216
223
  if status_category == RUN_STATUS_PASS and finding_count == 0:
@@ -489,8 +496,8 @@ def render_effect_report_text(report: Mapping[str, Any]) -> str:
489
496
  "",
490
497
  "## Provider/Lens Cells",
491
498
  "",
492
- "| Provider | Lens | Runs | Blocked caught/missed | Input gaps | Clean pass/false block | Effective catch | Evaluable catch | False-blocker rate | Sec/run |",
493
- "| --- | --- | ---: | --- | ---: | --- | ---: | ---: | ---: | ---: |",
499
+ "| Provider | Lens | Runs | Blocked caught/missed | Input gaps | Stale | Clean pass/false block | Effective catch | Evaluable catch | False-blocker rate | Sec/run |",
500
+ "| --- | --- | ---: | --- | ---: | ---: | --- | ---: | ---: | ---: | ---: |",
494
501
  ]
495
502
  for cell in report.get("cells", []) or []:
496
503
  if not isinstance(cell, Mapping):
@@ -504,6 +511,7 @@ def render_effect_report_text(report: Mapping[str, Any]) -> str:
504
511
  str(cell.get("runs", 0)),
505
512
  f"{cell.get('known_blocked_caught_runs', 0)}/{cell.get('known_blocked_missed_runs', 0)}",
506
513
  str(cell.get("audit_input_insufficient_runs", 0)),
514
+ str(cell.get("stale_runs", 0)),
507
515
  f"{cell.get('known_clean_pass_runs', 0)}/{cell.get('blocking_false_positive_runs', 0)}",
508
516
  _format_rate(cell.get("effective_catch_rate")),
509
517
  _format_rate(cell.get("catch_rate")),
@@ -12,6 +12,7 @@ from .run_status import (
12
12
  RUN_STATUS_BLOCKED,
13
13
  RUN_STATUS_INFRA_ERROR,
14
14
  RUN_STATUS_PASS,
15
+ RUN_STATUS_STALE,
15
16
  RUN_STATUS_UNKNOWN,
16
17
  normalize_run_status_category,
17
18
  )
@@ -77,6 +78,7 @@ def build_reviewer_evidence_report(corpus: Mapping[str, Any]) -> dict[str, Any]:
77
78
  profile_known_blocked_missed_run_keys: dict[str, set[tuple[Any, ...]]] = {}
78
79
  profile_audit_input_insufficient_run_keys: dict[str, set[tuple[Any, ...]]] = {}
79
80
  profile_infra_error_run_keys: dict[str, set[tuple[Any, ...]]] = {}
81
+ profile_stale_run_keys: dict[str, set[tuple[Any, ...]]] = {}
80
82
  profile_run_statuses: dict[str, dict[str, int]] = {}
81
83
  profile_automated_vs_manual: dict[str, dict[str, int]] = {}
82
84
  profile_review_classes: dict[str, set[str]] = {}
@@ -303,6 +305,8 @@ def build_reviewer_evidence_report(corpus: Mapping[str, Any]) -> dict[str, Any]:
303
305
  profile_infra_error_run_keys.setdefault(reviewer, set()).add(
304
306
  reviewer_run_key
305
307
  )
308
+ if status_category == RUN_STATUS_STALE:
309
+ profile_stale_run_keys.setdefault(reviewer, set()).add(reviewer_run_key)
306
310
  if status_category == RUN_STATUS_AUDIT_INPUT_INSUFFICIENT:
307
311
  profile_audit_input_insufficient_run_keys.setdefault(
308
312
  reviewer, set()
@@ -358,6 +362,7 @@ def build_reviewer_evidence_report(corpus: Mapping[str, Any]) -> dict[str, Any]:
358
362
  profile_known_blocked_missed_run_keys.get(reviewer, set())
359
363
  ),
360
364
  "infra_error_runs": len(profile_infra_error_run_keys.get(reviewer, set())),
365
+ "stale_runs": len(profile_stale_run_keys.get(reviewer, set())),
361
366
  "audit_input_insufficient_runs": len(
362
367
  profile_audit_input_insufficient_run_keys.get(reviewer, set())
363
368
  ),
@@ -32,6 +32,7 @@ def build_lane_policy_report(metrics: Mapping[str, Any]) -> dict[str, Any]:
32
32
  false_positive_runs = int(stats.get("blocking_false_positive_runs") or 0)
33
33
  known_blocked_missed_runs = int(stats.get("known_blocked_missed_runs") or 0)
34
34
  infra_error_runs = int(stats.get("infra_error_runs") or 0)
35
+ stale_runs = int(stats.get("stale_runs") or 0)
35
36
  audit_input_insufficient_runs = int(
36
37
  stats.get("audit_input_insufficient_runs") or 0
37
38
  )
@@ -158,6 +159,7 @@ def build_lane_policy_report(metrics: Mapping[str, Any]) -> dict[str, Any]:
158
159
  ),
159
160
  "known_blocked_missed_runs": known_blocked_missed_runs,
160
161
  "infra_error_runs": infra_error_runs,
162
+ "stale_runs": stale_runs,
161
163
  "audit_input_insufficient_runs": audit_input_insufficient_runs,
162
164
  "observed_pr_count": observed_pr_count,
163
165
  "reasons": reasons
@@ -9,6 +9,7 @@ RUN_STATUS_PASS = "pass"
9
9
  RUN_STATUS_BLOCKED = "blocked"
10
10
  RUN_STATUS_AUDIT_INPUT_INSUFFICIENT = "audit_input_insufficient"
11
11
  RUN_STATUS_INFRA_ERROR = "infra_error"
12
+ RUN_STATUS_STALE = "stale"
12
13
  RUN_STATUS_UNKNOWN = "unknown"
13
14
 
14
15
  RUN_STATUS_CATEGORY_ALIASES = {
@@ -36,7 +37,9 @@ RUN_STATUS_CATEGORY_ALIASES = {
36
37
  "rate_limit": RUN_STATUS_INFRA_ERROR,
37
38
  "rate_limited": RUN_STATUS_INFRA_ERROR,
38
39
  "setup_error": RUN_STATUS_INFRA_ERROR,
39
- "stale": RUN_STATUS_INFRA_ERROR,
40
+ "stale": RUN_STATUS_STALE,
41
+ "superseded": RUN_STATUS_STALE,
42
+ "superseded_head": RUN_STATUS_STALE,
40
43
  "timeout": RUN_STATUS_INFRA_ERROR,
41
44
  "timed_out": RUN_STATUS_INFRA_ERROR,
42
45
  }
@@ -56,8 +56,8 @@ def render_value_report_text(report: Mapping[str, Any]) -> str:
56
56
  f"Run dispositions: {report.get('run_disposition_count', 0)}",
57
57
  f"Reviewer runs: {report.get('reviewer_run_count', 0)}",
58
58
  "",
59
- "| Reviewer | Runs | Useful | Negative | Useful rate | Known-clean pass | Known-blocked caught/missed | Infra errors | Input gaps | Auto/manual M/MB/FB | Cost | Sec/run | Cost/useful | Policy | Recommended role |",
60
- "| --- | ---: | ---: | ---: | ---: | ---: | --- | ---: | ---: | --- | ---: | ---: | ---: | --- | --- |",
59
+ "| Reviewer | Runs | Useful | Negative | Useful rate | Known-clean pass | Known-blocked caught/missed | Infra errors | Stale | Input gaps | Auto/manual M/MB/FB | Cost | Sec/run | Cost/useful | Policy | Recommended role |",
60
+ "| --- | ---: | ---: | ---: | ---: | ---: | --- | ---: | ---: | ---: | --- | ---: | ---: | ---: | --- | --- |",
61
61
  ]
62
62
  if isinstance(profiles, Mapping) and profiles:
63
63
  for profile_id, stats in sorted(profiles.items()):
@@ -89,6 +89,7 @@ def render_value_report_text(report: Mapping[str, Any]) -> str:
89
89
  str(stats.get("known_clean_pass_runs", 0)),
90
90
  caught_missed,
91
91
  str(stats.get("infra_error_runs", 0)),
92
+ str(stats.get("stale_runs", 0)),
92
93
  str(stats.get("audit_input_insufficient_runs", 0)),
93
94
  auto_manual,
94
95
  str(stats.get("cost_usd", 0)),
@@ -109,7 +110,7 @@ def render_value_report_text(report: Mapping[str, Any]) -> str:
109
110
  + " |"
110
111
  )
111
112
  else:
112
- lines.append("| none | 0 | 0 | 0 | | 0 | 0/0 | 0 | 0 | 0/0/0 | 0 | | | | |")
113
+ lines.append("| none | 0 | 0 | 0 | | 0 | 0/0 | 0 | 0 | 0 | 0/0/0 | 0 | | | | |")
113
114
 
114
115
  recommendations = metrics.get("recommendations", []) if isinstance(metrics, Mapping) else []
115
116
  lines.extend(["", "## Recommendations"])
@@ -188,6 +189,7 @@ def render_value_report_html(report: Mapping[str, Any]) -> str:
188
189
  f"<td>{esc(stats.get('negative_findings', 0))}</td>"
189
190
  f"<td>{esc(stats.get('useful_rate', ''))}</td>"
190
191
  f"<td>{esc(caught_missed)}</td>"
192
+ f"<td>{esc(stats.get('stale_runs', 0))}</td>"
191
193
  f"<td>{esc(auto_manual)}</td>"
192
194
  f"<td>{esc(stats.get('cost_usd', 0))}</td>"
193
195
  f"<td>{esc(stats.get('seconds_per_run', ''))}</td>"
@@ -195,7 +197,7 @@ def render_value_report_html(report: Mapping[str, Any]) -> str:
195
197
  "</tr>"
196
198
  )
197
199
  else:
198
- rows.append("<tr><td colspan='10'>No reviewer rows yet.</td></tr>")
200
+ rows.append("<tr><td colspan='11'>No reviewer rows yet.</td></tr>")
199
201
 
200
202
  recommendation_items = (
201
203
  "\n".join(f"<li>{esc(item)}</li>" for item in recommendations)
@@ -242,7 +244,7 @@ def render_value_report_html(report: Mapping[str, Any]) -> str:
242
244
  " </section>\n"
243
245
  " <section class='card'>\n"
244
246
  " <h2>Reviewer / lens signal</h2>\n"
245
- " <table><thead><tr><th>Reviewer</th><th>Runs</th><th>Useful</th><th>Negative</th><th>Useful rate</th><th>Blocked caught/missed</th><th>Auto/manual</th><th>Cost</th><th>Sec/run</th><th>Role</th></tr></thead>\n"
247
+ " <table><thead><tr><th>Reviewer</th><th>Runs</th><th>Useful</th><th>Negative</th><th>Useful rate</th><th>Blocked caught/missed</th><th>Stale</th><th>Auto/manual</th><th>Cost</th><th>Sec/run</th><th>Role</th></tr></thead>\n"
246
248
  f" <tbody>{''.join(rows)}</tbody></table>\n"
247
249
  " </section>\n"
248
250
  " <section class='card'>\n"
@@ -209,6 +209,7 @@ class ClaudeAuditConfig:
209
209
  max_plan_context_bytes: int = code_mower_plan_context.DEFAULT_MAX_TOTAL_BYTES
210
210
  max_plan_context_file_bytes: int = code_mower_plan_context.DEFAULT_MAX_FILE_BYTES
211
211
  merge_authority: bool = True
212
+ calibration_badge: str = ""
212
213
 
213
214
 
214
215
  @dataclass
@@ -1026,10 +1027,13 @@ def format_comment(
1026
1027
  merge_authority: bool = True,
1027
1028
  actions_run_id: Optional[str] = None,
1028
1029
  unknown_reason: str = "",
1030
+ calibration_badge: str = "",
1029
1031
  ) -> str:
1030
1032
  posture = "merge-authority lane" if merge_authority else "informational only"
1031
1033
  header = f"## Claude audit ({posture})\n\n"
1032
1034
  header += f"Head SHA: `{head_sha}`\n"
1035
+ if badge := _normalize_calibration_badge(calibration_badge):
1036
+ header += f"Calibration: {badge}\n"
1033
1037
  if actions_run_id:
1034
1038
  header += f"{AUDIT_RUN_TRAILER_PREFIX} run_id={actions_run_id} -->\n"
1035
1039
  if is_stale:
@@ -1079,6 +1083,10 @@ def format_comment(
1079
1083
  return limit_comment_body(body, trailer, provider_name="Claude")
1080
1084
 
1081
1085
 
1086
+ def _normalize_calibration_badge(value: str | None) -> str:
1087
+ return " ".join(str(value or "").strip().split())[:120]
1088
+
1089
+
1082
1090
  def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAuditResult:
1083
1091
  audit_started = time.monotonic()
1084
1092
  local_repo = config.repo_paths.get(repo)
@@ -1148,6 +1156,7 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
1148
1156
  stale_end_sha=exc.actual_sha,
1149
1157
  merge_authority=config.merge_authority,
1150
1158
  actions_run_id=actions_run_id,
1159
+ calibration_badge=config.calibration_badge,
1151
1160
  )
1152
1161
  result = ClaudeAuditResult(
1153
1162
  repo=repo,
@@ -1334,6 +1343,7 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
1334
1343
  stale_end_sha=head_sha_end,
1335
1344
  merge_authority=config.merge_authority,
1336
1345
  actions_run_id=actions_run_id,
1346
+ calibration_badge=config.calibration_badge,
1337
1347
  )
1338
1348
  result_verdict = "STALE"
1339
1349
  trailer = STALE_TRAILER
@@ -1345,6 +1355,7 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
1345
1355
  merge_authority=config.merge_authority,
1346
1356
  actions_run_id=actions_run_id,
1347
1357
  unknown_reason=claude_cli_reason,
1358
+ calibration_badge=config.calibration_badge,
1348
1359
  )
1349
1360
  result_verdict = "UNKNOWN"
1350
1361
  trailer = STALE_TRAILER
@@ -1354,6 +1365,7 @@ def audit_pr(config: ClaudeAuditConfig, repo: str, pr_number: int) -> ClaudeAudi
1354
1365
  head_sha_start,
1355
1366
  merge_authority=config.merge_authority,
1356
1367
  actions_run_id=actions_run_id,
1368
+ calibration_badge=config.calibration_badge,
1357
1369
  )
1358
1370
  result_verdict = parsed.verdict
1359
1371
  trailer = BLOCKED_TRAILER if parsed.verdict == "BLOCKED" else DONE_TRAILER
@@ -1611,7 +1623,15 @@ def _parse_args(argv: Optional[List[str]] = None) -> argparse.Namespace:
1611
1623
  "--informational",
1612
1624
  dest="merge_authority",
1613
1625
  action="store_false",
1614
- help="Render audit comments as informational-only calibration comments.",
1626
+ help="Render audit comments as informational-only lane comments.",
1627
+ )
1628
+ ap.add_argument(
1629
+ "--calibration-badge",
1630
+ default=os.environ.get("CLAUDE_AUDIT_CALIBRATION_BADGE", ""),
1631
+ help=(
1632
+ "Render a separate calibration status badge line without changing "
1633
+ "the lane's merge-authority posture."
1634
+ ),
1615
1635
  )
1616
1636
  return ap.parse_args(argv)
1617
1637
 
@@ -1673,6 +1693,7 @@ def main(argv: Optional[List[str]] = None) -> int:
1673
1693
  max_plan_context_bytes=args.max_plan_context_bytes,
1674
1694
  max_plan_context_file_bytes=args.max_plan_context_file_bytes,
1675
1695
  merge_authority=args.merge_authority,
1696
+ calibration_badge=args.calibration_badge,
1676
1697
  )
1677
1698
  result = audit_pr(config, args.repo, args.pr)
1678
1699
  except (OSError, RuntimeError, ValueError, subprocess.CalledProcessError) as exc:
@@ -1705,7 +1726,7 @@ def main(argv: Optional[List[str]] = None) -> int:
1705
1726
 
1706
1727
 
1707
1728
  def _audit_exit_code(verdict: str) -> int:
1708
- return 2 if verdict == "UNKNOWN" else 0
1729
+ return 2 if str(verdict or "").upper() == "UNKNOWN" else 0
1709
1730
 
1710
1731
 
1711
1732
  if __name__ == "__main__":
@@ -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.0b47`, or run source checkouts with "
17
+ "`pipx install code-mower==0.5.0b49`, or run source checkouts with "
18
18
  "`PYTHONPATH=src python -m code_mower.cli`."
19
19
  )
20
20
 
@@ -20,6 +20,7 @@ if __package__ in {None, ""}:
20
20
  RUN_STATUS_CATEGORY_ALIASES,
21
21
  RUN_STATUS_INFRA_ERROR,
22
22
  RUN_STATUS_PASS,
23
+ RUN_STATUS_STALE,
23
24
  RUN_STATUS_UNKNOWN,
24
25
  TRUTH_EXPECTATION_ALIASES,
25
26
  TRUTH_EXPECTATION_KNOWN_BLOCKED,
@@ -89,6 +90,7 @@ if __package__ in {None, ""}:
89
90
  RUN_STATUS_CATEGORY_ALIASES,
90
91
  RUN_STATUS_INFRA_ERROR,
91
92
  RUN_STATUS_PASS,
93
+ RUN_STATUS_STALE,
92
94
  RUN_STATUS_UNKNOWN,
93
95
  TRUTH_EXPECTATION_ALIASES,
94
96
  TRUTH_EXPECTATION_KNOWN_BLOCKED,
@@ -158,6 +160,7 @@ elif __package__ == "tools":
158
160
  RUN_STATUS_CATEGORY_ALIASES,
159
161
  RUN_STATUS_INFRA_ERROR,
160
162
  RUN_STATUS_PASS,
163
+ RUN_STATUS_STALE,
161
164
  RUN_STATUS_UNKNOWN,
162
165
  TRUTH_EXPECTATION_ALIASES,
163
166
  TRUTH_EXPECTATION_KNOWN_BLOCKED,
@@ -227,6 +230,7 @@ else: # pragma: no cover - exercised after package extraction.
227
230
  RUN_STATUS_CATEGORY_ALIASES,
228
231
  RUN_STATUS_INFRA_ERROR,
229
232
  RUN_STATUS_PASS,
233
+ RUN_STATUS_STALE,
230
234
  RUN_STATUS_UNKNOWN,
231
235
  TRUTH_EXPECTATION_ALIASES,
232
236
  TRUTH_EXPECTATION_KNOWN_BLOCKED,
@@ -296,6 +300,7 @@ _LEGACY_RUN_STATUS_EXPORTS = (
296
300
  RUN_STATUS_CATEGORY_ALIASES,
297
301
  RUN_STATUS_INFRA_ERROR,
298
302
  RUN_STATUS_PASS,
303
+ RUN_STATUS_STALE,
299
304
  RUN_STATUS_UNKNOWN,
300
305
  _normalize_run_status_category,
301
306
  )