maintainability-agent 2.0.0__tar.gz → 2.2.0__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.
- {maintainability_agent-2.0.0/src/maintainability_agent.egg-info → maintainability_agent-2.2.0}/PKG-INFO +2 -2
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/README.md +1 -1
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/pyproject.toml +1 -1
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0/src/maintainability_agent.egg-info}/PKG-INFO +2 -2
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_agent.egg-info/SOURCES.txt +7 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/__init__.py +1 -1
- maintainability_agent-2.2.0/src/maintainability_audit/_conformance.py +183 -0
- maintainability_agent-2.2.0/src/maintainability_audit/_gates.py +122 -0
- maintainability_agent-2.2.0/src/maintainability_audit/_ratchet.py +109 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/cli.py +94 -23
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/config.py +1 -1
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/git_tools.py +38 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/instructions.py +57 -12
- maintainability_agent-2.2.0/tests/test_added_lines.py +123 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_anticipated_refusals.py +7 -0
- maintainability_agent-2.2.0/tests/test_dimension_ratchet.py +155 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_identity_resolution.py +5 -1
- maintainability_agent-2.2.0/tests/test_instruction_targets.py +154 -0
- maintainability_agent-2.2.0/tests/test_scope_conformance.py +301 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/LICENSE +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/setup.cfg +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_agent.egg-info/dependency_links.txt +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_agent.egg-info/entry_points.txt +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_agent.egg-info/requires.txt +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_agent.egg-info/top_level.txt +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/__main__.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_adapters.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_analysis.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_aspects.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_assets/analyzer-catalog.json +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_assets/standard.md +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_backfill.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_bands.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_built_ins.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_calibration.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_catalog.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_charts.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_cognitive.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_config_defaults.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_corroborate.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_coverage_notes.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_derive.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_discovery.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_documents.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_economics.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_economics_view.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_environment.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_evidence_view.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_finding_match.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_first_run.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_formula.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_generic.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_grant_ledger.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_history_view.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_hostile_prompt.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_hotspots.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_html_report_sections.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_html_view.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_identity.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_jvm_adapters.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_markdown_sections.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_masking.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_mcp_audit.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_mcp_gate.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_mcp_grants.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_mcp_refusals.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_mcp_resources.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_mcp_setup.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_metric_adapters.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_metrics_types.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_pillars.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_practice.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_pressures.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_ranges_c.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_ranges_core.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_ranges_cpp.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_ranges_csharp.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_ranges_fortran.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_ranges_java.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_ranges_js.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_recurrence.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_runner.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_safe_write.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_scan_history.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_scan_view.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_second_source.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_selection.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_semantic.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_semantic_policy.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_semantic_ts.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_semantic_view.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_setup_errors.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_setup_persist.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_skill_data/SKILL.md +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_skill_data/agents/anthropic.yaml +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_skill_data/agents/copilot.yaml +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_skill_data/agents/openai.yaml +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_skill_data/copilot/maintainability-agent.prompt.md +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_skill_data/references/finding-taxonomy.md +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_skill_install.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_stored_grants.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_tdd_view.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_test_execution.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_test_pairing.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_tokens.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_tool_adapters.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_trends.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_user_config.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_verdict_adapters.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_verification.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_work_order.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_work_order_view.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_work_order_weights.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_xml.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/baseline.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/deadcode.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/declarations.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/duplication.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/evidence.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/history.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/idioms.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/mcp_server.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/metrics.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/prompts.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/renderers.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/report.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/sarif.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/scoring.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/similarity.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/source.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_acquisition_trust.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_adapter_docs.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_adapter_recordings.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_adapters.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analysis_coverage.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analysis_execution.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_bridge.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_config_isolation.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_estimate_claims.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_pinning.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_primary_estimate.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_provenance.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_provenance_exclusions.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_scoring_path.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_spread_range.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_xml_bounds.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_annotations_match_behaviour.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_architecture.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_artifact_write_route_class.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_aspect_coverage.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_asset_snapshot_class.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_ast_reading_resolves_spellings.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_audit_components.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_authorization_freshness.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_authorship_gates.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_backfill.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_band_concealment_class.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_band_concealment_p3.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_band_pressures.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_bands.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_body_or_signature_class.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_c_declarations.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_calibration_corpus.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_calibration_drift.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_calibration_tooling.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_catalog_selection.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_catalog_tier_promise.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_chat_primary_docs.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_checkstyle_adapter.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_checkstyle_catalog.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_ci_history_recipe.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_ci_installs_the_analyzer_pool.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_claimed_languages.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_cli.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_clone_group_class.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_cognitive_complexity.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_cohort_statistics.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_concealment_properties.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_config_shape.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_consent_and_grant.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_consumer_migration.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_contract_claims.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_corroborate.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_coverage_by_language.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_cpp_declarations.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_csharp_declarations.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_d15_composition.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_d15_goal_directed.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_dead_code.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_declaration_edge_cases.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_declaration_grading.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_declaration_ranges.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_declaration_source.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_declared_authorship.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_declared_imports.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_derivation_mix.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_determinism.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_direct_runner_availability_class.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_discovery.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_doc_claims.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_docs_links.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_economic_context.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_empty_output_not_clean.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_empty_output_not_clean_adapters.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_environment_work_order.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_evidence_invariants.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_evidence_normalization.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_evidence_properties.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_exclusion_dialects.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_falsifier_standard.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_file_list_cap_class.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_finding_identity.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_first_run_elicitation.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_first_run_prompt.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_first_run_reelicitation.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_fix_breadth_window.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_fixed_form_fortran.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_format_ask.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_fortran_declarations.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_fortran_metrics.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_fortran_practice.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_git_argv.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_git_read_only.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_git_readonly_class.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_grant_only_user_tier.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_history.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_history_boundary.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_history_manifest.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_history_schema2.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_history_window.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_hostile_prompt.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_html_executive.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_html_section_parity_class.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_identity_docs.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_idiom_divergence.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_installed_wheel_runtime.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_java_range_guard.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_java_ranges.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_java_scored.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_java_wiring.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_js_complexity_operators.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_js_declarations.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_language_coverage.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_mcp_baseline_payload.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_mcp_history.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_mcp_server.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_migration_1_0.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_near_duplicates.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_network_disclosure.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_one_setup_across_surfaces_class.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_pattern_budget.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_phase6_claims.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_pillars_and_practice.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_platform_claim.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_pmd_adapter.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_pmd_audit_closures.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_pool_runs_by_config.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_population_floors.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_practice_nested_gates_class.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_presentation_refusals.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_promises.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_prompt_escalation_once.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_prompt_honesty.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_prompt_location_escalation_class.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_prove_falsifiers.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_python_declarations.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_read_boundary.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_readme_claims.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_recurrence.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_release_plan.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_release_plan_status.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_report_remedy_copy.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_roles_recorded.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_root_grants.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_rubric_ignores_project_lint_config.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_runner.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_sarif.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_scan_history.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_scan_scope.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_scanning.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_schema_fidelity.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_scoring_calibration.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_semantic_policy.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_setup_gate_completeness.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_setup_gate_independent_contract.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_setup_precondition.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_skill_install.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_source_index.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_spotbugs_adapter.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_spotbugs_catalog.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_stage8_contract.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_tdd_structure.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_test_execution_class.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_three_presentations.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_trends.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_type_fields_not_functions_class.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_uat_falsifiers.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_unread_code.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_user_config_tier.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_verification_audit_closures.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_verified_grade.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_version_claim.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_wheel_contents.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_work_order.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_work_order_items.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_work_order_selection.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_workflow_supply_chain.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_write_boundary.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_write_route_class.py +0 -0
- {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_written_record.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: maintainability-agent
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Maintainability gate and AI remediation prompt generator for CI.
|
|
5
5
|
Author: Marshall Guillory
|
|
6
6
|
License: MIT
|
|
@@ -44,7 +44,7 @@ Dynamic: license-file
|
|
|
44
44
|
**A deterministic, offline maintainability audit whose output is a _bounded
|
|
45
45
|
work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
|
|
46
46
|
says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
|
|
47
|
-
Version **2.
|
|
47
|
+
Version **2.2.0**.
|
|
48
48
|
|
|
49
49
|
**Languages parsed:** Python, Java, C, C++, C#, Fortran (free-form *and*
|
|
50
50
|
fixed-form), and the JS/TS family — each by a scanner written for it, and
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
**A deterministic, offline maintainability audit whose output is a _bounded
|
|
6
6
|
work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
|
|
7
7
|
says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
|
|
8
|
-
Version **2.
|
|
8
|
+
Version **2.2.0**.
|
|
9
9
|
|
|
10
10
|
**Languages parsed:** Python, Java, C, C++, C#, Fortran (free-form *and*
|
|
11
11
|
fixed-form), and the JS/TS family — each by a scanner written for it, and
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "maintainability-agent"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.2.0"
|
|
8
8
|
description = "Maintainability gate and AI remediation prompt generator for CI."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
# 3.11, not 3.10. `_discovery`, `_pillars` and `_runner` all import
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: maintainability-agent
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Maintainability gate and AI remediation prompt generator for CI.
|
|
5
5
|
Author: Marshall Guillory
|
|
6
6
|
License: MIT
|
|
@@ -44,7 +44,7 @@ Dynamic: license-file
|
|
|
44
44
|
**A deterministic, offline maintainability audit whose output is a _bounded
|
|
45
45
|
work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
|
|
46
46
|
says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
|
|
47
|
-
Version **2.
|
|
47
|
+
Version **2.2.0**.
|
|
48
48
|
|
|
49
49
|
**Languages parsed:** Python, Java, C, C++, C#, Fortran (free-form *and*
|
|
50
50
|
fixed-form), and the JS/TS family — each by a scanner written for it, and
|
|
@@ -20,6 +20,7 @@ src/maintainability_audit/_catalog.py
|
|
|
20
20
|
src/maintainability_audit/_charts.py
|
|
21
21
|
src/maintainability_audit/_cognitive.py
|
|
22
22
|
src/maintainability_audit/_config_defaults.py
|
|
23
|
+
src/maintainability_audit/_conformance.py
|
|
23
24
|
src/maintainability_audit/_corroborate.py
|
|
24
25
|
src/maintainability_audit/_coverage_notes.py
|
|
25
26
|
src/maintainability_audit/_derive.py
|
|
@@ -32,6 +33,7 @@ src/maintainability_audit/_evidence_view.py
|
|
|
32
33
|
src/maintainability_audit/_finding_match.py
|
|
33
34
|
src/maintainability_audit/_first_run.py
|
|
34
35
|
src/maintainability_audit/_formula.py
|
|
36
|
+
src/maintainability_audit/_gates.py
|
|
35
37
|
src/maintainability_audit/_generic.py
|
|
36
38
|
src/maintainability_audit/_grant_ledger.py
|
|
37
39
|
src/maintainability_audit/_history_view.py
|
|
@@ -61,6 +63,7 @@ src/maintainability_audit/_ranges_csharp.py
|
|
|
61
63
|
src/maintainability_audit/_ranges_fortran.py
|
|
62
64
|
src/maintainability_audit/_ranges_java.py
|
|
63
65
|
src/maintainability_audit/_ranges_js.py
|
|
66
|
+
src/maintainability_audit/_ratchet.py
|
|
64
67
|
src/maintainability_audit/_recurrence.py
|
|
65
68
|
src/maintainability_audit/_runner.py
|
|
66
69
|
src/maintainability_audit/_safe_write.py
|
|
@@ -121,6 +124,7 @@ tests/test_acquisition_trust.py
|
|
|
121
124
|
tests/test_adapter_docs.py
|
|
122
125
|
tests/test_adapter_recordings.py
|
|
123
126
|
tests/test_adapters.py
|
|
127
|
+
tests/test_added_lines.py
|
|
124
128
|
tests/test_analysis_coverage.py
|
|
125
129
|
tests/test_analysis_execution.py
|
|
126
130
|
tests/test_analyzer_bridge.py
|
|
@@ -185,6 +189,7 @@ tests/test_declared_authorship.py
|
|
|
185
189
|
tests/test_declared_imports.py
|
|
186
190
|
tests/test_derivation_mix.py
|
|
187
191
|
tests/test_determinism.py
|
|
192
|
+
tests/test_dimension_ratchet.py
|
|
188
193
|
tests/test_direct_runner_availability_class.py
|
|
189
194
|
tests/test_discovery.py
|
|
190
195
|
tests/test_doc_claims.py
|
|
@@ -225,6 +230,7 @@ tests/test_identity_docs.py
|
|
|
225
230
|
tests/test_identity_resolution.py
|
|
226
231
|
tests/test_idiom_divergence.py
|
|
227
232
|
tests/test_installed_wheel_runtime.py
|
|
233
|
+
tests/test_instruction_targets.py
|
|
228
234
|
tests/test_java_range_guard.py
|
|
229
235
|
tests/test_java_ranges.py
|
|
230
236
|
tests/test_java_scored.py
|
|
@@ -270,6 +276,7 @@ tests/test_scan_history.py
|
|
|
270
276
|
tests/test_scan_scope.py
|
|
271
277
|
tests/test_scanning.py
|
|
272
278
|
tests/test_schema_fidelity.py
|
|
279
|
+
tests/test_scope_conformance.py
|
|
273
280
|
tests/test_scoring_calibration.py
|
|
274
281
|
tests/test_semantic_policy.py
|
|
275
282
|
tests/test_setup_gate_completeness.py
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Did the diff stay inside the work order it was given?
|
|
2
|
+
|
|
3
|
+
The product's central claim is a bounded work order: *fix exactly these
|
|
4
|
+
findings and refactor nothing else*. Until now that bound was an
|
|
5
|
+
**instruction**. The prompt said it, and nothing checked it — so an agent
|
|
6
|
+
that rewrote half the tree while closing one finding produced a diff
|
|
7
|
+
indistinguishable, to this tool, from one that did as it was told.
|
|
8
|
+
|
|
9
|
+
This is the check that makes the bound verifiable. It reads the finished
|
|
10
|
+
report and a revspec, and reports which changed files the work order
|
|
11
|
+
actually named. It is the first of the three mechanisms behind the
|
|
12
|
+
attestation artifact, and the one a code-generating agent cannot honestly
|
|
13
|
+
perform about itself.
|
|
14
|
+
|
|
15
|
+
**What it must not do is cry wolf.** A correct remediation almost always
|
|
16
|
+
touches a file the work order does not name: the test that proves the fix.
|
|
17
|
+
A check that flagged every good remediation would be turned off within a
|
|
18
|
+
week, so a test file is in scope when it pairs to a named path by the same
|
|
19
|
+
convention `_test_pairing` already uses for coverage — `test_foo.py` pairs
|
|
20
|
+
to `foo.py`. A test paired to nothing named is still reported, because
|
|
21
|
+
"while I was here I rewrote the suite" is exactly the drift being watched
|
|
22
|
+
for.
|
|
23
|
+
|
|
24
|
+
Three deliberate limits:
|
|
25
|
+
|
|
26
|
+
- **It never scores.** Whether a diff was obedient is a fact about an
|
|
27
|
+
agent's behaviour, not evidence about the code's condition, so nothing
|
|
28
|
+
here reaches scoring or moves a grade.
|
|
29
|
+
- **It reports, it does not gate**, unless a caller asks it to. A finding
|
|
30
|
+
can legitimately require touching a caller the work order did not name,
|
|
31
|
+
and a check that cannot be argued with becomes a check that is bypassed.
|
|
32
|
+
- **It compares paths, not intent.** A file being in scope says the work
|
|
33
|
+
order named it, not that the change to it was the right one. Review is
|
|
34
|
+
still review.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import re
|
|
40
|
+
from typing import Any
|
|
41
|
+
|
|
42
|
+
from ._metrics_types import is_test_path
|
|
43
|
+
from ._test_pairing import subject_stem
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _named_paths(work_order: list[dict[str, Any]]) -> set[str]:
|
|
47
|
+
"""Every path the work order put in front of the agent."""
|
|
48
|
+
return {
|
|
49
|
+
str(item["path"]) for item in work_order
|
|
50
|
+
if isinstance(item, dict) and item.get("path")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _pairs_to_named(path: str, named: set[str]) -> bool:
|
|
55
|
+
"""Whether a test file covers something the work order named.
|
|
56
|
+
|
|
57
|
+
Uses the pairing convention the coverage aspect already applies, so a
|
|
58
|
+
remediation's own test is in scope without the caller declaring it —
|
|
59
|
+
and a test that covers nothing in the work order is not.
|
|
60
|
+
"""
|
|
61
|
+
if not is_test_path(path):
|
|
62
|
+
return False
|
|
63
|
+
subject = subject_stem(path)
|
|
64
|
+
if not subject:
|
|
65
|
+
return False
|
|
66
|
+
return any(subject_stem(candidate) == subject for candidate in named)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
#: Markers that switch a checker off rather than satisfy it. Per language,
|
|
70
|
+
#: because the vocabulary differs and a single regex over all of them
|
|
71
|
+
#: matches prose: "# type: ignore" in a docstring explaining the convention
|
|
72
|
+
#: is not a suppression, and neither is this comment.
|
|
73
|
+
#:
|
|
74
|
+
#: Deliberately narrow. A marker here has to be a real directive that a
|
|
75
|
+
#: real tool obeys; guessing wider would report ordinary comments as
|
|
76
|
+
#: evasion, and a check that cries wolf is a check that gets switched off —
|
|
77
|
+
#: the same reasoning as the test-pairing rule above.
|
|
78
|
+
SUPPRESSION_MARKERS: tuple[tuple[str, str], ...] = (
|
|
79
|
+
(r"#\s*noqa\b", "noqa"),
|
|
80
|
+
(r"#\s*type:\s*ignore\b", "type: ignore"),
|
|
81
|
+
(r"#\s*pragma:\s*no\s*cover\b", "pragma: no cover"),
|
|
82
|
+
(r"#\s*nosec\b", "nosec"),
|
|
83
|
+
(r"//\s*eslint-disable", "eslint-disable"),
|
|
84
|
+
(r"/\*\s*eslint-disable", "eslint-disable"),
|
|
85
|
+
(r"//\s*@ts-(ignore|expect-error)\b", "ts-ignore"),
|
|
86
|
+
(r"@SuppressWarnings\b", "SuppressWarnings"),
|
|
87
|
+
(r"//\s*NOSONAR\b", "NOSONAR"),
|
|
88
|
+
(r"#\s*pylint:\s*disable\b", "pylint: disable"),
|
|
89
|
+
(r"@pytest\.mark\.(skip|xfail)\b", "skipped test"),
|
|
90
|
+
(r"@unittest\.skip\b", "skipped test"),
|
|
91
|
+
(r"\bit\.skip\(|\bdescribe\.skip\(", "skipped test"),
|
|
92
|
+
(r"@Disabled\b", "disabled test"),
|
|
93
|
+
(r"@Ignore\b", "ignored test"),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
_SUPPRESSION = tuple(
|
|
97
|
+
(re.compile(pattern, re.IGNORECASE), label) for pattern, label in SUPPRESSION_MARKERS
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def suppressions_added(
|
|
102
|
+
added: dict[str, list[tuple[int, str]]], named: set[str]
|
|
103
|
+
) -> list[dict[str, Any]]:
|
|
104
|
+
"""Suppressions this change introduced, and whether they land on a finding.
|
|
105
|
+
|
|
106
|
+
The failure being watched for: a finding is closed by making it
|
|
107
|
+
invisible rather than by fixing it. Add `# noqa` to the flagged line and
|
|
108
|
+
the next audit reports nothing — the scan goes green over code nobody
|
|
109
|
+
repaired, and scope conformance alone would call that diff obedient,
|
|
110
|
+
because the file it touched is exactly the file the work order named.
|
|
111
|
+
|
|
112
|
+
Only *added* lines are read. A suppression already in the tree is not
|
|
113
|
+
evidence about this change, and treating a decade of accumulated
|
|
114
|
+
directives as newly written would drown the signal that matters.
|
|
115
|
+
|
|
116
|
+
`on_named_path` is that signal: a suppression added to a file the work
|
|
117
|
+
order flagged is the shape of a finding being silenced, while one added
|
|
118
|
+
elsewhere is ordinary engineering that a reviewer may still want to see.
|
|
119
|
+
"""
|
|
120
|
+
found: list[dict[str, Any]] = []
|
|
121
|
+
for path in sorted(added):
|
|
122
|
+
for line_number, text in added[path]:
|
|
123
|
+
for pattern, label in _SUPPRESSION:
|
|
124
|
+
if pattern.search(text):
|
|
125
|
+
found.append({
|
|
126
|
+
"path": path,
|
|
127
|
+
"line": line_number,
|
|
128
|
+
"marker": label,
|
|
129
|
+
"on_named_path": path in named,
|
|
130
|
+
})
|
|
131
|
+
break
|
|
132
|
+
return found
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def scope_conformance(
|
|
136
|
+
report: dict[str, Any],
|
|
137
|
+
changed: set[str],
|
|
138
|
+
revspec: str,
|
|
139
|
+
added: dict[str, list[tuple[int, str]]] | None = None,
|
|
140
|
+
) -> dict[str, Any]:
|
|
141
|
+
"""How a diff relates to the work order the agent was handed.
|
|
142
|
+
|
|
143
|
+
Returns the record, never a verdict on the code. `in_scope`,
|
|
144
|
+
`paired_tests` and `out_of_scope` partition the changed files;
|
|
145
|
+
`unaddressed` names work-order paths the diff did not touch, which is
|
|
146
|
+
not a failure — a bounded change may take one item at a time.
|
|
147
|
+
"""
|
|
148
|
+
work_order = report.get("work_order") or []
|
|
149
|
+
named = _named_paths(work_order)
|
|
150
|
+
|
|
151
|
+
in_scope = sorted(path for path in changed if path in named)
|
|
152
|
+
remaining = {path for path in changed if path not in named}
|
|
153
|
+
paired = sorted(path for path in remaining if _pairs_to_named(path, named))
|
|
154
|
+
out_of_scope = sorted(remaining - set(paired))
|
|
155
|
+
suppressions = suppressions_added(added or {}, named)
|
|
156
|
+
silenced = [item for item in suppressions if item["on_named_path"]]
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
"revspec": revspec,
|
|
160
|
+
"work_order_items": len(work_order),
|
|
161
|
+
"named_paths": sorted(named),
|
|
162
|
+
"changed_paths": sorted(changed),
|
|
163
|
+
"in_scope": in_scope,
|
|
164
|
+
"paired_tests": paired,
|
|
165
|
+
"out_of_scope": out_of_scope,
|
|
166
|
+
"unaddressed": sorted(named - set(in_scope)),
|
|
167
|
+
"suppressions_added": suppressions,
|
|
168
|
+
"suppressions_on_named_paths": len(silenced),
|
|
169
|
+
# Two questions, kept apart. `conformant` answers "did it change
|
|
170
|
+
# only what it was asked to"; `clean` also answers "and without
|
|
171
|
+
# switching a checker off in a file that was flagged". Scope alone
|
|
172
|
+
# is satisfiable by adding `# noqa` to the named file and changing
|
|
173
|
+
# nothing else, which is the evasion this pairing exists to catch.
|
|
174
|
+
"conformant": not out_of_scope,
|
|
175
|
+
"clean": not out_of_scope and not silenced,
|
|
176
|
+
# Stated so a reader does not infer more than was checked. The
|
|
177
|
+
# record says which files the work order named, not whether the
|
|
178
|
+
# edits inside them were the right ones.
|
|
179
|
+
"note": (
|
|
180
|
+
"Paths and added lines. A file in scope means the work order "
|
|
181
|
+
"named it, not that the change to it was correct."
|
|
182
|
+
),
|
|
183
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""What happens after the audit and before the exit code.
|
|
2
|
+
|
|
3
|
+
Split from `cli.py` when it crossed this project's 500-line file gate —
|
|
4
|
+
the same reason `_mcp_setup`, `_adapters` and `test_architecture` were
|
|
5
|
+
split before it.
|
|
6
|
+
|
|
7
|
+
The split is not arbitrary. Everything here composes from the **finished
|
|
8
|
+
report**: whether the diff obeyed its work order, whether a dimension
|
|
9
|
+
slipped since the last comparable scan, and what those two facts mean for
|
|
10
|
+
the process exit code. None of it measures a tree and none of it may reach
|
|
11
|
+
scoring — a fact about an agent's behaviour, or about the difference
|
|
12
|
+
between two scans, is not evidence about the code's condition.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from .baseline import findings_not_in_baseline
|
|
22
|
+
from .git_tools import added_lines, changed_paths
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def attach_conformance(args: argparse.Namespace, report: dict) -> None:
|
|
26
|
+
"""Record how the diff relates to the work order, when asked.
|
|
27
|
+
|
|
28
|
+
Assembly, not scoring: the record is attached to the report and reaches
|
|
29
|
+
no dimension, because whether a diff was obedient is a fact about an
|
|
30
|
+
agent's behaviour and not evidence about the code's condition.
|
|
31
|
+
"""
|
|
32
|
+
if not args.conformance:
|
|
33
|
+
return
|
|
34
|
+
from ._conformance import scope_conformance
|
|
35
|
+
|
|
36
|
+
root = Path(report["root"])
|
|
37
|
+
changed = changed_paths(root, args.conformance)
|
|
38
|
+
added = added_lines(root, args.conformance)
|
|
39
|
+
report["scope_conformance"] = scope_conformance(
|
|
40
|
+
report, changed, args.conformance, added
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def attach_ratchet(report: dict, history_path: Path) -> None:
|
|
45
|
+
"""Record whether any category slipped since the last comparable scan."""
|
|
46
|
+
from ._ratchet import dimension_ratchet
|
|
47
|
+
from ._scan_history import read_history
|
|
48
|
+
|
|
49
|
+
if not history_path.exists():
|
|
50
|
+
return
|
|
51
|
+
report["dimension_ratchet"] = dimension_ratchet(read_history(history_path))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _attach_post_audit_records(
|
|
55
|
+
args: argparse.Namespace, report: dict, history_path: Path
|
|
56
|
+
) -> None:
|
|
57
|
+
"""Records composed from the finished report, before it is rendered.
|
|
58
|
+
|
|
59
|
+
Both read the report rather than the tree, and neither reaches scoring:
|
|
60
|
+
whether a diff obeyed its work order, and whether a dimension slipped
|
|
61
|
+
since the last comparable scan, are facts about a change and its
|
|
62
|
+
history — not evidence about the code's condition.
|
|
63
|
+
"""
|
|
64
|
+
attach_conformance(args, report)
|
|
65
|
+
attach_ratchet(report, history_path)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _conformance_exit(args: argparse.Namespace, report: dict) -> int:
|
|
69
|
+
"""0 to continue; otherwise the exit code the conformance gate demands."""
|
|
70
|
+
if not args.fail_on_out_of_scope:
|
|
71
|
+
return 0
|
|
72
|
+
record = report.get("scope_conformance")
|
|
73
|
+
if record is None:
|
|
74
|
+
print("--fail-on-out-of-scope needs --conformance REVSPEC", file=sys.stderr)
|
|
75
|
+
return 2
|
|
76
|
+
# `clean`, not `conformant`: staying in scope while silencing the finding
|
|
77
|
+
# is the evasion the pairing exists to catch, and a gate that accepted it
|
|
78
|
+
# would be satisfied by the thing it guards against.
|
|
79
|
+
return 0 if record["clean"] else 1
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _regression_exit(args: argparse.Namespace, report: dict) -> int:
|
|
83
|
+
"""0 to continue; otherwise the exit code the ratchet demands."""
|
|
84
|
+
if not args.fail_on_regression:
|
|
85
|
+
return 0
|
|
86
|
+
ratchet = report.get("dimension_ratchet")
|
|
87
|
+
if ratchet is None:
|
|
88
|
+
print("--fail-on-regression needs a recorded history (--record-history)",
|
|
89
|
+
file=sys.stderr)
|
|
90
|
+
return 2
|
|
91
|
+
# A run that could not compare does not pass quietly: exiting 0 where the
|
|
92
|
+
# question was never asked is the "absence read as a pass" shape.
|
|
93
|
+
#
|
|
94
|
+
# The reason is not interpolated — it derives from recorded scan history,
|
|
95
|
+
# and code scanning is right that history fields have no business in a log
|
|
96
|
+
# line. It stays in the report, which a caller reads deliberately.
|
|
97
|
+
# Suppressing that alert was refused: this release ships the check that
|
|
98
|
+
# treats a suppression as a finding rather than a fix.
|
|
99
|
+
if not ratchet["comparable"]:
|
|
100
|
+
print("no comparable scan for --fail-on-regression; see "
|
|
101
|
+
"dimension_ratchet.reason in the report", file=sys.stderr)
|
|
102
|
+
return 2
|
|
103
|
+
return 1 if ratchet["regressed"] else 0
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def audit_exit_code(args: argparse.Namespace, report: dict) -> int:
|
|
107
|
+
if args.fail_on_new:
|
|
108
|
+
# Structured matching, never a label-set difference: a label
|
|
109
|
+
# changes under `git mv` and same-name reorder, and failing a
|
|
110
|
+
# build over either is a false report about the change.
|
|
111
|
+
root = Path(report["root"])
|
|
112
|
+
if findings_not_in_baseline(report, args.baseline, root):
|
|
113
|
+
return 1
|
|
114
|
+
if args.fail_on_gate and report["hard_gate_failures"]:
|
|
115
|
+
return 1
|
|
116
|
+
conformance = _conformance_exit(args, report)
|
|
117
|
+
if conformance:
|
|
118
|
+
return conformance
|
|
119
|
+
regression = _regression_exit(args, report)
|
|
120
|
+
if regression:
|
|
121
|
+
return regression
|
|
122
|
+
return 0
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Did any dimension get worse than the last comparable scan?
|
|
2
|
+
|
|
3
|
+
The third mechanism behind the attestation. `--fail-on-new` already catches
|
|
4
|
+
a finding that clears and returns, using structured identity so a rename is
|
|
5
|
+
not a new finding. What nothing caught is the slower failure: a change that
|
|
6
|
+
improves one dimension while quietly regressing another. Both scans are
|
|
7
|
+
green by every existing gate, the overall estimate can even rise, and the
|
|
8
|
+
thing that got worse is never named.
|
|
9
|
+
|
|
10
|
+
**The comparison is only honest inside one segment.** History is already
|
|
11
|
+
split wherever the instrument changed — a different calibration constant, a
|
|
12
|
+
different analyzer population — because two scans taken with different
|
|
13
|
+
instruments cannot be subtracted. `_scan_history.segments` owns that rule,
|
|
14
|
+
and this module refuses rather than reaching across a break. A ratchet that
|
|
15
|
+
compared a 1.x score to a 2.0 score would have reported every repository on
|
|
16
|
+
earth as catastrophically regressed on the day the corpus was extended, and
|
|
17
|
+
it would have been the ratchet that was wrong.
|
|
18
|
+
|
|
19
|
+
So there are three outcomes, not two: **held**, **regressed**, and **not
|
|
20
|
+
comparable**. The third is not a pass. It says the question could not be
|
|
21
|
+
asked, which is what a reader needs in order to know they are not being
|
|
22
|
+
reassured by silence.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
from ._scan_history import ScanRecord, segments
|
|
30
|
+
|
|
31
|
+
#: Scores are reported to one decimal, so a difference below this is the
|
|
32
|
+
#: rounding boundary rather than a movement. Named rather than inlined
|
|
33
|
+
#: because it is a judgment: too tight and every scan trips on noise, too
|
|
34
|
+
#: loose and a real slide hides under the threshold.
|
|
35
|
+
TOLERANCE = 0.05
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _series(record: ScanRecord, field: str) -> dict[str, float]:
|
|
39
|
+
values = getattr(record, field, None) or {}
|
|
40
|
+
return {
|
|
41
|
+
name: float(value) for name, value in values.items()
|
|
42
|
+
if isinstance(value, (int, float))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _regressions(
|
|
47
|
+
previous: ScanRecord, current: ScanRecord, field: str
|
|
48
|
+
) -> list[dict[str, Any]]:
|
|
49
|
+
"""Names that dropped, with the size of the drop."""
|
|
50
|
+
before, after = _series(previous, field), _series(current, field)
|
|
51
|
+
found = []
|
|
52
|
+
for name, was in sorted(before.items()):
|
|
53
|
+
# A name the current scan does not carry is not a regression: the
|
|
54
|
+
# aspect became unmeasurable, which the evidence model already
|
|
55
|
+
# reports as `Unknown` rather than as a zero. Scoring it as a drop
|
|
56
|
+
# to nothing would turn withheld evidence into a failure, which is
|
|
57
|
+
# the exact inversion P3 forbids.
|
|
58
|
+
if name not in after:
|
|
59
|
+
continue
|
|
60
|
+
now = after[name]
|
|
61
|
+
if was - now > TOLERANCE:
|
|
62
|
+
found.append({
|
|
63
|
+
"name": name, "was": round(was, 2),
|
|
64
|
+
"now": round(now, 2), "drop": round(was - now, 2),
|
|
65
|
+
})
|
|
66
|
+
return found
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def dimension_ratchet(records: list[ScanRecord]) -> dict[str, Any]:
|
|
70
|
+
"""Compare the newest scan with the previous comparable one.
|
|
71
|
+
|
|
72
|
+
`comparable` is the load-bearing field. False means the two scans sit
|
|
73
|
+
either side of an instrument change, or there is no earlier scan at
|
|
74
|
+
all — in both cases the question was not asked, and a caller must not
|
|
75
|
+
read that as "nothing regressed".
|
|
76
|
+
"""
|
|
77
|
+
if len(records) < 2:
|
|
78
|
+
return {
|
|
79
|
+
"comparable": False,
|
|
80
|
+
"reason": "no earlier scan to compare against",
|
|
81
|
+
"regressed": [],
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
latest = segments(records)[-1]
|
|
85
|
+
if len(latest.records) < 2:
|
|
86
|
+
return {
|
|
87
|
+
"comparable": False,
|
|
88
|
+
"reason": (
|
|
89
|
+
latest.break_reason
|
|
90
|
+
or "the previous scan was taken with a different instrument"
|
|
91
|
+
),
|
|
92
|
+
"regressed": [],
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
previous, current = latest.records[-2], latest.records[-1]
|
|
96
|
+
categories = _regressions(previous, current, "categories")
|
|
97
|
+
aspects = _regressions(previous, current, "aspects")
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
"comparable": True,
|
|
101
|
+
"against": getattr(previous, "commit", None),
|
|
102
|
+
"categories": categories,
|
|
103
|
+
"aspects": aspects,
|
|
104
|
+
# Categories only. An aspect can move while the category holding it
|
|
105
|
+
# holds, and failing on every aspect wobble makes the gate noise.
|
|
106
|
+
# Aspects are reported so a reader can see what moved underneath.
|
|
107
|
+
"regressed": categories,
|
|
108
|
+
"held": not categories,
|
|
109
|
+
}
|