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