maintainability-agent 3.2.0__tar.gz → 3.3.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-3.2.0/src/maintainability_agent.egg-info → maintainability_agent-3.3.0}/PKG-INFO +2 -2
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/README.md +1 -1
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/pyproject.toml +1 -1
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0/src/maintainability_agent.egg-info}/PKG-INFO +2 -2
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_agent.egg-info/SOURCES.txt +2 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/__init__.py +1 -1
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_arguments.py +9 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_coverage_notes.py +36 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_criteria_scope.py +37 -31
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_delegated_pillar.py +24 -1
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_html_report_sections.py +23 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_mcp_audit.py +36 -4
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_pillars.py +7 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_scan_history.py +94 -5
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_test_execution.py +81 -15
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/cli.py +2 -1
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/config.py +1 -1
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/duplication.py +21 -10
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/history.py +58 -28
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/report.py +8 -5
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_bands.py +80 -0
- maintainability_agent-3.3.0/tests/test_child_sandbox.py +203 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_delegated_security_pillar.py +72 -2
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_docs_links.py +30 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_html_section_parity_class.py +21 -1
- maintainability_agent-3.3.0/tests/test_mcp_format_contract.py +22 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_mcp_server.py +33 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_operator_named_reads.py +5 -1
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_test_execution_class.py +28 -8
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_tree_cannot_opt_the_host_in.py +61 -3
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/LICENSE +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/setup.cfg +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_agent.egg-info/dependency_links.txt +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_agent.egg-info/entry_points.txt +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_agent.egg-info/requires.txt +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_agent.egg-info/top_level.txt +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/__main__.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_adapters.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_analysis.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_analyzer_sections.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_anchor.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_aspects.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_assets/analyzer-catalog.json +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_assets/standard.md +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_attestation.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_backfill.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_bands.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_banner.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_built_ins.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_calibration.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_catalog.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_charts.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_cognitive.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_config_defaults.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_conformance.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_corroborate.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_derive.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_discovery.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_documents.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_economics.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_economics_view.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_environment.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_evidence_reader.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_evidence_view.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_finding_match.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_first_run.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_formula.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_gates.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_generic.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_grammar.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_grant_ledger.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_history_view.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_hostile_prompt.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_hotspots.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_html_view.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_identity.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_in_loop.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_in_loop_view.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_jvm_adapters.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_markdown_sections.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_masking.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_mcp_gate.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_mcp_grants.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_mcp_refusals.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_mcp_resources.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_mcp_setup.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_metric_adapters.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_metrics_types.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_operator_reads.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_practice.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_precommit.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_precommit_install.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_precommit_view.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_pressures.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_prompt_sections.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_c.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_cobol.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_core.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_cpp.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_csharp.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_fortran.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_go.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_java.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_js.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_php.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_ruby.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_rust.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_swift.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ratchet.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ratio_adapters.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_recurrence.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_run_comparison.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_runner.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_safe_write.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_scan_view.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_second_source.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_selection.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_semantic.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_semantic_policy.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_semantic_ts.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_semantic_view.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_setup_errors.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_setup_persist.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_skill_data/SKILL.md +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_skill_data/agents/anthropic.yaml +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_skill_data/agents/copilot.yaml +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_skill_data/agents/openai.yaml +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_skill_data/copilot/maintainability-agent.prompt.md +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_skill_data/references/finding-taxonomy.md +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_skill_install.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_stored_grants.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_tdd_view.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_test_commands.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_test_pairing.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_tokens.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_tool_adapters.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_trends.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_user_config.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_verdict_adapters.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_verification.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_work_order.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_work_order_view.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_work_order_weights.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_xml.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/baseline.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/deadcode.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/declarations.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/evidence.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/git_tools.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/idioms.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/instructions.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/mcp_server.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/metrics.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/prompts.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/renderers.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/sarif.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/scoring.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/similarity.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/source.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_acquisition_trust.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_adapter_docs.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_adapter_recordings.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_adapters.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_added_lines.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analysis_coverage.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analysis_execution.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_bridge.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_config_isolation.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_estimate_claims.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_pinning.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_primary_estimate.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_provenance.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_provenance_exclusions.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_scoring_path.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_spread_range.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_xml_bounds.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_anchor_and_mode.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_anchor_disclosure.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_annotations_match_behaviour.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_anticipated_refusals.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_architecture.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_artifact_write_route_class.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_aspect_coverage.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_asset_snapshot_class.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_ast_reading_resolves_spellings.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_attestation.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_audit_components.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_authorization_freshness.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_authorship_gates.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_backfill.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_band_concealment_class.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_band_concealment_p3.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_band_pressures.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_body_or_signature_class.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_c_declarations.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_calibration_corpus.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_calibration_drift.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_calibration_tooling.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_catalog_selection.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_catalog_tier_promise.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_chat_primary_docs.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_check_refuses_plus_only_fragments.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_checkstyle_adapter.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_checkstyle_catalog.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_ci_history_recipe.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_ci_installs_the_analyzer_pool.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_claimed_languages.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_cli.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_clone_group_class.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_cobol_declarations.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_cognitive_complexity.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_cohort_statistics.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_concealment_properties.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_config_shape.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_consent_and_grant.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_consumer_migration.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_contract_claims.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_corroborate.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_coverage_by_language.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_cpp_declarations.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_criteria_completion.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_csharp_declarations.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_d15_composition.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_d15_goal_directed.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_dead_code.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_declaration_criteria_scope.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_declaration_edge_cases.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_declaration_grading.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_declaration_ranges.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_declaration_source.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_declarative_parsers.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_declared_authorship.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_declared_imports.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_derivation_mix.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_determinism.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_dimension_ratchet.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_direct_runner_availability_class.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_discovery.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_doc_claims.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_economic_context.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_empty_output_not_clean.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_empty_output_not_clean_adapters.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_environment_work_order.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_evidence_invariants.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_evidence_normalization.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_evidence_properties.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_exclusion_dialects.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_falsifier_standard.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_file_list_cap_class.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_finding_identity.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_first_run_elicitation.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_first_run_prompt.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_first_run_reelicitation.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_fix_breadth_window.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_fixed_form_fortran.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_format_ask.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_fortran_declarations.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_fortran_metrics.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_fortran_practice.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_git_argv.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_git_read_only.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_git_readonly_class.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_go_declarations.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_grammar_constructs.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_grammar_token_coverage.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_grant_only_user_tier.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_history.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_history_boundary.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_history_manifest.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_history_schema2.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_history_window.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_hostile_prompt.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_html_executive.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_identity_docs.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_identity_resolution.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_idiom_divergence.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_in_loop_budgets.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_in_loop_check.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_installed_wheel_runtime.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_instruction_targets.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_java_range_guard.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_java_ranges.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_java_scored.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_java_wiring.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_js_complexity_operators.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_js_declarations.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_language_coverage.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_language_support_honesty.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_mcp_baseline_payload.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_mcp_history.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_migration_1_0.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_near_duplicates.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_network_disclosure.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_one_setup_across_surfaces_class.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_operator_named_paths.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_pairing_reads_imports.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_pattern_budget.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_phase6_claims.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_php_declarations.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_pillars_and_practice.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_platform_claim.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_pmd_adapter.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_pmd_audit_closures.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_policy_claims.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_pool_runs_by_config.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_population_floors.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_practice_nested_gates_class.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_precommit_staged.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_presentation_refusals.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_promises.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_prompt_escalation_once.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_prompt_honesty.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_prompt_location_escalation_class.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_prove_falsifiers.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_python_complexity.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_python_declarations.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_read_boundary.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_readme_claims.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_recurrence.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_release_needs_an_empty_ledger.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_release_plan.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_release_plan_status.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_report_remedy_copy.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_roles_recorded.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_root_grants.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_rubric_ignores_project_lint_config.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_ruby_declarations.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_run_comparison.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_runner.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_rust_declarations.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_sarif.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_scan_history.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_scan_scope.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_scanner_unsafe_direction.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_scanning.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_schema_fidelity.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_scope_conformance.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_scoring_calibration.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_semantic_policy.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_setup_gate_completeness.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_setup_gate_independent_contract.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_setup_precondition.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_skill_install.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_sonar_resolve.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_source_index.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_spotbugs_adapter.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_spotbugs_catalog.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_stage8_contract.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_suite_isolation.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_swift_declarations.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_tdd_structure.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_terminal_asks_the_same_questions.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_test_commands.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_three_presentations.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_tool_resolution.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_tree_chosen_spawn.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_tree_reads_use_the_primitive.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_trends.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_type_fields_not_functions_class.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_uat_falsifiers.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_unanchored_set_matches_corpus.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_unread_code.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_user_config_tier.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_verification_audit_closures.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_verified_grade.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_version_claim.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_wheel_contents.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_work_order.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_work_order_binds_to_this_interpreter.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_work_order_items.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_work_order_selection.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_workflow_supply_chain.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_write_boundary.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_write_route_class.py +0 -0
- {maintainability_agent-3.2.0 → maintainability_agent-3.3.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: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: Maintainability gate and AI remediation prompt generator for CI.
|
|
5
5
|
Author: Marshall Guillory
|
|
6
6
|
License: MIT
|
|
@@ -45,7 +45,7 @@ Dynamic: license-file
|
|
|
45
45
|
**A deterministic, offline maintainability audit whose output is a _bounded
|
|
46
46
|
work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
|
|
47
47
|
says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
|
|
48
|
-
Version **3.
|
|
48
|
+
Version **3.3.0**.
|
|
49
49
|
|
|
50
50
|
**Planned, not shipped:** the audit tool will become `maintainability-audit`;
|
|
51
51
|
a new `maintainability-agent` companion will run it and automate human-selected
|
|
@@ -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 **3.
|
|
8
|
+
Version **3.3.0**.
|
|
9
9
|
|
|
10
10
|
**Planned, not shipped:** the audit tool will become `maintainability-audit`;
|
|
11
11
|
a new `maintainability-agent` companion will run it and automate human-selected
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "maintainability-agent"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.3.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: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: Maintainability gate and AI remediation prompt generator for CI.
|
|
5
5
|
Author: Marshall Guillory
|
|
6
6
|
License: MIT
|
|
@@ -45,7 +45,7 @@ Dynamic: license-file
|
|
|
45
45
|
**A deterministic, offline maintainability audit whose output is a _bounded
|
|
46
46
|
work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
|
|
47
47
|
says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
|
|
48
|
-
Version **3.
|
|
48
|
+
Version **3.3.0**.
|
|
49
49
|
|
|
50
50
|
**Planned, not shipped:** the audit tool will become `maintainability-audit`;
|
|
51
51
|
a new `maintainability-agent` companion will run it and automate human-selected
|
|
@@ -191,6 +191,7 @@ tests/test_chat_primary_docs.py
|
|
|
191
191
|
tests/test_check_refuses_plus_only_fragments.py
|
|
192
192
|
tests/test_checkstyle_adapter.py
|
|
193
193
|
tests/test_checkstyle_catalog.py
|
|
194
|
+
tests/test_child_sandbox.py
|
|
194
195
|
tests/test_ci_history_recipe.py
|
|
195
196
|
tests/test_ci_installs_the_analyzer_pool.py
|
|
196
197
|
tests/test_claimed_languages.py
|
|
@@ -279,6 +280,7 @@ tests/test_js_declarations.py
|
|
|
279
280
|
tests/test_language_coverage.py
|
|
280
281
|
tests/test_language_support_honesty.py
|
|
281
282
|
tests/test_mcp_baseline_payload.py
|
|
283
|
+
tests/test_mcp_format_contract.py
|
|
282
284
|
tests/test_mcp_history.py
|
|
283
285
|
tests/test_mcp_server.py
|
|
284
286
|
tests/test_migration_1_0.py
|
{maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_arguments.py
RENAMED
|
@@ -120,6 +120,15 @@ def add_arguments(parser: argparse.ArgumentParser) -> None:
|
|
|
120
120
|
parser.add_argument("--output", help="Output file. Defaults to stdout.")
|
|
121
121
|
_add_artifact_arguments(parser)
|
|
122
122
|
parser.add_argument("--sarif-output", help="Optional SARIF output path for GitHub code scanning.")
|
|
123
|
+
parser.add_argument(
|
|
124
|
+
"--security-pillar",
|
|
125
|
+
help=(
|
|
126
|
+
"Path to the security-pillar.json secure-code-agent writes with its "
|
|
127
|
+
"own --security-pillar. ADR 007 delegates the security pillar, and "
|
|
128
|
+
"this is the document it is read from; defaults to "
|
|
129
|
+
".maintainability/security-pillar.json."
|
|
130
|
+
),
|
|
131
|
+
)
|
|
123
132
|
parser.add_argument("--sarif-input", action="append", help="Optional external SARIF file to summarize in reports. Repeatable.")
|
|
124
133
|
parser.add_argument("--changed-only", help="Audit only files changed in a git revspec, for example main...HEAD.")
|
|
125
134
|
parser.add_argument("--baseline", help="Existing baseline JSON. With --fail-on-new, only new findings fail.")
|
|
@@ -59,4 +59,40 @@ def coverage_notes(coverage: dict[str, Any]) -> list[str]:
|
|
|
59
59
|
"or widen `analyzers.depth`, to have them reported.",
|
|
60
60
|
"",
|
|
61
61
|
])
|
|
62
|
+
|
|
63
|
+
lines.extend(_child_boundary_note(coverage))
|
|
62
64
|
return lines
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _child_boundary_note(coverage: dict[str, Any]) -> list[str]:
|
|
68
|
+
"""What this run does not control about the tools it spawned.
|
|
69
|
+
|
|
70
|
+
`docs/product-intent.md` has said since P1 was amended that this
|
|
71
|
+
package "does not sandbox children", and lists *"that third-party
|
|
72
|
+
tools cannot use the network"* among the things it does not promise.
|
|
73
|
+
The **report** did not say it. A reader holding a page headed "the
|
|
74
|
+
analysis itself performs no network access" and a table naming the
|
|
75
|
+
external tools that ran had no way to learn the second sentence does
|
|
76
|
+
not cover the first.
|
|
77
|
+
|
|
78
|
+
That makes it P8's gap rather than P1's: the promise was correctly
|
|
79
|
+
scoped all along, and the run relying on that scoping never disclosed
|
|
80
|
+
it to the person reading the output.
|
|
81
|
+
|
|
82
|
+
Only when an external tool actually ran. With no children spawned
|
|
83
|
+
there is nothing to disclose, and saying it anyway would be the noise
|
|
84
|
+
this section is careful to avoid.
|
|
85
|
+
"""
|
|
86
|
+
spawned = coverage.get("tools_contributed") or 0
|
|
87
|
+
if not spawned:
|
|
88
|
+
return []
|
|
89
|
+
return [
|
|
90
|
+
"**Analyzer children are not network-isolated.** "
|
|
91
|
+
f"{spawned} external {'tool' if spawned == 1 else 'tools'} ran as local "
|
|
92
|
+
"child processes. This agent does not transmit the audited source and "
|
|
93
|
+
"opens no socket of its own, but it does not sandbox what it spawns: a "
|
|
94
|
+
"third-party analyzer that reaches the network is outside what this run "
|
|
95
|
+
"controls or observes. Determinism and no upload are the promise; a "
|
|
96
|
+
"kernel air-gap is not.",
|
|
97
|
+
"",
|
|
98
|
+
]
|
|
@@ -154,40 +154,46 @@ def complete_from_built_ins(
|
|
|
154
154
|
"""
|
|
155
155
|
completed: dict[str, dict[str, float]] = {}
|
|
156
156
|
for unit, values in per_unit.items():
|
|
157
|
-
|
|
158
|
-
if not
|
|
159
|
-
completed[unit] =
|
|
160
|
-
continue
|
|
161
|
-
location = unit_locations.get(unit)
|
|
162
|
-
fallback = built_in.get(location) if location else None
|
|
163
|
-
filled = dict(values)
|
|
164
|
-
for concept in missing:
|
|
165
|
-
if fallback and concept in fallback:
|
|
166
|
-
filled[concept] = fallback[concept]
|
|
167
|
-
if declaration_concepts_missing(set(filled)):
|
|
168
|
-
# No counterpart in the scanner's reading, so this
|
|
169
|
-
# declaration is not scoreable on the full criterion set by
|
|
170
|
-
# either tier — and it is dropped rather than left to
|
|
171
|
-
# disqualify its language.
|
|
172
|
-
#
|
|
173
|
-
# Measured, because the alternative looked reasonable and was
|
|
174
|
-
# not: `lizard` reads files the built-in scanner produces no
|
|
175
|
-
# declarations for — Alamofire's `docs/js/jquery.min.js` and
|
|
176
|
-
# `jazzy.js`, five generated documentation assets. Keeping
|
|
177
|
-
# them gave JavaScript a bucket that could never be completed
|
|
178
|
-
# and took the whole repository to the built-in tier, Swift
|
|
179
|
-
# and all. The same shape hid Java behind a repository's
|
|
180
|
-
# bundled `.js`.
|
|
181
|
-
#
|
|
182
|
-
# Dropping them also aligns the two populations: a unit the
|
|
183
|
-
# scanner never read is not in the built-in rate either, so
|
|
184
|
-
# including it in the analyzer rate compares a wider
|
|
185
|
-
# population against a narrower one.
|
|
186
|
-
continue
|
|
187
|
-
completed[unit] = filled
|
|
157
|
+
filled = _completed_unit(values, unit_locations.get(unit), built_in)
|
|
158
|
+
if filled is not None:
|
|
159
|
+
completed[unit] = filled
|
|
188
160
|
return completed
|
|
189
161
|
|
|
190
162
|
|
|
163
|
+
def _completed_unit(
|
|
164
|
+
values: dict[str, float],
|
|
165
|
+
location: tuple[str, int] | None,
|
|
166
|
+
built_in: dict[tuple[str, int], dict[str, float]],
|
|
167
|
+
) -> dict[str, float] | None:
|
|
168
|
+
"""One declaration's readings, completed from the built-in tier.
|
|
169
|
+
|
|
170
|
+
`None` means the union of both tiers still does not cover the
|
|
171
|
+
criterion set, so this declaration is **dropped** rather than left to
|
|
172
|
+
disqualify its language.
|
|
173
|
+
|
|
174
|
+
Measured, because the alternative looked reasonable and was not:
|
|
175
|
+
`lizard` reads files the built-in scanner produces no declarations
|
|
176
|
+
for — Alamofire's `docs/js/jquery.min.js` and `jazzy.js`, five
|
|
177
|
+
generated documentation assets. Keeping them gave JavaScript a bucket
|
|
178
|
+
that could never be completed and took the whole repository to the
|
|
179
|
+
built-in tier, Swift and all. The same shape hid Java behind a
|
|
180
|
+
repository's bundled `.js`.
|
|
181
|
+
|
|
182
|
+
Dropping them also aligns the two populations: a unit the scanner
|
|
183
|
+
never read is not in the built-in rate either, so including it in the
|
|
184
|
+
analyzer rate compares a wider population against a narrower one.
|
|
185
|
+
"""
|
|
186
|
+
missing = declaration_concepts_missing(set(values))
|
|
187
|
+
if not missing:
|
|
188
|
+
return values
|
|
189
|
+
fallback = (built_in.get(location) if location else None) or {}
|
|
190
|
+
filled = dict(values)
|
|
191
|
+
for concept in missing:
|
|
192
|
+
if concept in fallback:
|
|
193
|
+
filled[concept] = fallback[concept]
|
|
194
|
+
return None if declaration_concepts_missing(set(filled)) else filled
|
|
195
|
+
|
|
196
|
+
|
|
191
197
|
def built_in_readings(
|
|
192
198
|
metrics: object, root: str = ""
|
|
193
199
|
) -> dict[tuple[str, int], dict[str, float]]:
|
|
@@ -58,6 +58,19 @@ DEFAULT_PILLAR_PATH = ".maintainability/security-pillar.json"
|
|
|
58
58
|
#: producer's promise about the shape, and guessing past it is how a
|
|
59
59
|
#: consumer starts reporting fields that mean something different.
|
|
60
60
|
SCHEMA = "secure-code-agent/security-pillar"
|
|
61
|
+
#: Versions of that contract this tool can read. **Two, deliberately.**
|
|
62
|
+
#: v2 is v1 plus `scoring_model` and nothing else, so a reader that
|
|
63
|
+
#: handles both is not guessing: the producer asserts the key-set
|
|
64
|
+
#: difference is exactly that one field.
|
|
65
|
+
#:
|
|
66
|
+
#: Accepting v2 before the producer emits it is what removes the
|
|
67
|
+
#: deadlock. If MA required v1 while `secure-code-agent` shipped v2,
|
|
68
|
+
#: every document would be refused for the window between the two
|
|
69
|
+
#: releases -- silently, since a refused document reads as no delegated
|
|
70
|
+
#: pillar. Either side can now release first.
|
|
71
|
+
SCHEMA_VERSIONS = frozenset({1, 2})
|
|
72
|
+
|
|
73
|
+
#: Kept for callers that imported the old name. The gate reads the set.
|
|
61
74
|
SCHEMA_VERSION = 1
|
|
62
75
|
|
|
63
76
|
#: What the pillar block takes from the document. Named rather than
|
|
@@ -75,6 +88,16 @@ CARRIED = (
|
|
|
75
88
|
"findings_by_severity",
|
|
76
89
|
"reported_not_scored",
|
|
77
90
|
"loc_scanned",
|
|
91
|
+
#: v2. The producer's own identifier for its scoring model, bumped
|
|
92
|
+
#: when a repository's condition could differ for reasons that are
|
|
93
|
+
#: not the repository -- normalizer, slope, weights, letter bands,
|
|
94
|
+
#: the scanner floor, or the built-in rule profile. Absent on v1.
|
|
95
|
+
#:
|
|
96
|
+
#: This is what MA keys a delegated trend on, in preference to the
|
|
97
|
+
#: producer's version, because the version moves on docs releases
|
|
98
|
+
#: that change no score and fragmenting a trend for those teaches a
|
|
99
|
+
#: reader to ignore the breaks (D155).
|
|
100
|
+
"scoring_model",
|
|
78
101
|
)
|
|
79
102
|
|
|
80
103
|
|
|
@@ -104,7 +127,7 @@ def read_delegated(root: Path, relative: str = DEFAULT_PILLAR_PATH) -> dict[str,
|
|
|
104
127
|
return None
|
|
105
128
|
if payload.get("schema") != SCHEMA:
|
|
106
129
|
return None
|
|
107
|
-
if payload.get("schema_version")
|
|
130
|
+
if payload.get("schema_version") not in SCHEMA_VERSIONS:
|
|
108
131
|
return None
|
|
109
132
|
return payload if _is_well_formed(payload) else None
|
|
110
133
|
|
|
@@ -14,6 +14,7 @@ from __future__ import annotations
|
|
|
14
14
|
from html import escape
|
|
15
15
|
from typing import Any
|
|
16
16
|
|
|
17
|
+
from ._coverage_notes import coverage_notes
|
|
17
18
|
from ._evidence_view import test_suite_lines
|
|
18
19
|
from ._grammar import counted
|
|
19
20
|
from ._hotspots import hotspot_cognitive, hotspot_complexity, hotspot_name
|
|
@@ -55,9 +56,31 @@ def coverage_section(report: dict[str, Any]) -> list[str]:
|
|
|
55
56
|
f"<td>{escape(str(entry.get('measurements', '—')))}</td>"
|
|
56
57
|
f"<td>{escape(str(entry.get('findings', '—')))}</td></tr>")
|
|
57
58
|
parts.append("</table>")
|
|
59
|
+
parts.extend(_coverage_notes_html(coverage))
|
|
58
60
|
return parts
|
|
59
61
|
|
|
60
62
|
|
|
63
|
+
def _coverage_notes_html(coverage: dict[str, Any]) -> list[str]:
|
|
64
|
+
"""The gap prose the Markdown skin prints, in the HTML skin too.
|
|
65
|
+
|
|
66
|
+
The table answers "what ran". These notes answer what a reader asks
|
|
67
|
+
next — one source only, a dimension the analyzer tier declined,
|
|
68
|
+
nothing examined — and the HTML report carried **none** of them,
|
|
69
|
+
including "Nothing examined", which `_coverage_notes` itself calls
|
|
70
|
+
the point of the whole section. ADR 011 says the skins render one
|
|
71
|
+
report dict and never disagree; that claim was resting on the prose
|
|
72
|
+
being built as Markdown strings inside the Markdown renderer.
|
|
73
|
+
|
|
74
|
+
Rendered as the same visible sentences rather than re-marked-up:
|
|
75
|
+
the notes carry `**bold**` and `` `code` `` for Markdown, and a
|
|
76
|
+
reader of the page should see the text, not the syntax.
|
|
77
|
+
"""
|
|
78
|
+
return [
|
|
79
|
+
f"<p>{escape(line.replace('**', '').replace('`', ''))}</p>"
|
|
80
|
+
for line in coverage_notes(coverage) if line
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
|
|
61
84
|
def _current_series_html(history: list[dict[str, Any]]) -> list[str]:
|
|
62
85
|
"""The series in force, in full — the one a reader acts on today."""
|
|
63
86
|
segment = history[-1]
|
{maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_mcp_audit.py
RENAMED
|
@@ -47,6 +47,7 @@ from ._work_order import prompt_targets
|
|
|
47
47
|
from .baseline import finding_fingerprints
|
|
48
48
|
from .config import (
|
|
49
49
|
VERSION,
|
|
50
|
+
_refuse_symlinked_route,
|
|
50
51
|
analyzers_run_default,
|
|
51
52
|
discovered_config,
|
|
52
53
|
load_config,
|
|
@@ -122,6 +123,20 @@ def _check_freshness_when_a_grant_is_the_only_cover(
|
|
|
122
123
|
|
|
123
124
|
|
|
124
125
|
def authorize_config(config_path: str | None, root: Path) -> str | None:
|
|
126
|
+
"""The config a caller named, or a refusal — bounded like every other path.
|
|
127
|
+
|
|
128
|
+
Two rules, and this door enforced only one of them. Containment on
|
|
129
|
+
the *resolved* path catches a config outside the repository. It
|
|
130
|
+
cannot catch an **inward** link — `root/link -> root/src` — because
|
|
131
|
+
that resolves to a path inside the repository and passes every
|
|
132
|
+
containment check there is. That escape shows on the lexical route
|
|
133
|
+
only, which is why `repository_path` walks it (D34).
|
|
134
|
+
|
|
135
|
+
So the two doors onto one rule disagreed: `repository_path` refused
|
|
136
|
+
`link/…` and this returned the target. The baseline seam below
|
|
137
|
+
already carries a comment recording this same escape being found and
|
|
138
|
+
closed *there*; this door was never revisited (Grok, 673e667).
|
|
139
|
+
"""
|
|
125
140
|
if config_path is None:
|
|
126
141
|
return None
|
|
127
142
|
config = _resolved(config_path, relative_to=root)
|
|
@@ -137,6 +152,11 @@ def authorize_config(config_path: str | None, root: Path) -> str | None:
|
|
|
137
152
|
"audited. A config file must live inside the repository it "
|
|
138
153
|
"configures."
|
|
139
154
|
)
|
|
155
|
+
# After containment, on the route as written rather than as resolved.
|
|
156
|
+
# `repository_path` orders it the same way, and the shared helper is
|
|
157
|
+
# what keeps the two doors from drifting apart again.
|
|
158
|
+
candidate = Path(config_path).expanduser()
|
|
159
|
+
_refuse_symlinked_route(root, candidate if candidate.is_absolute() else root / candidate)
|
|
140
160
|
return str(config)
|
|
141
161
|
|
|
142
162
|
|
|
@@ -415,10 +435,22 @@ def _finish_result(result: dict[str, Any], format: str, root: Path,
|
|
|
415
435
|
|
|
416
436
|
8.5: the host asked the user which presentation and passes the answer
|
|
417
437
|
here. One findings body per call (D8): json carries the report dict
|
|
418
|
-
and no rendering;
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
438
|
+
and no rendering; html adds the HTML text beside a Markdown payload,
|
|
439
|
+
returned for the host to save or show — files are the CLI's job
|
|
440
|
+
(ADR 011 §4).
|
|
441
|
+
|
|
442
|
+
**`chat` and `markdown` are not the same text on the wire**, and
|
|
443
|
+
this docstring said they were while the code beside it already
|
|
444
|
+
passed `complete=(format == "markdown")`. They are two skins:
|
|
445
|
+
|
|
446
|
+
- **chat** is the *bounded* skin — the inline UI view, trimmed to
|
|
447
|
+
stay under a host's payload cap so a large repository's response
|
|
448
|
+
is not truncated mid-report with the remediation prompt inside it;
|
|
449
|
+
- **markdown** is the *complete* report, the whole document, because
|
|
450
|
+
it was chosen as a file to keep.
|
|
451
|
+
|
|
452
|
+
A reader who believed the old sentence would have concluded the
|
|
453
|
+
bounding was a rendering accident rather than the contract.
|
|
422
454
|
"""
|
|
423
455
|
if format not in ("chat", "markdown", "html", "json"):
|
|
424
456
|
raise InvalidAuditArgument(f"format must be chat, markdown, html or json, not {format!r}")
|
{maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_pillars.py
RENAMED
|
@@ -155,6 +155,13 @@ CARRIED = (
|
|
|
155
155
|
"findings_by_severity",
|
|
156
156
|
"reported_not_scored",
|
|
157
157
|
"loc_scanned",
|
|
158
|
+
#: v2 of the contract. The producer's identifier for its scoring
|
|
159
|
+
#: model, which is what a delegated trend is keyed on (D155). Absent
|
|
160
|
+
#: on a v1 document, and `None` there rather than invented — the
|
|
161
|
+
#: history falls back to the producer version, which fragments
|
|
162
|
+
#: pre-field releases correctly instead of asserting they shared one
|
|
163
|
+
#: model.
|
|
164
|
+
"scoring_model",
|
|
158
165
|
)
|
|
159
166
|
|
|
160
167
|
|
|
@@ -76,6 +76,15 @@ COMPARABILITY_FIELDS: tuple[str, ...] = (
|
|
|
76
76
|
"analyzers",
|
|
77
77
|
"scored_languages",
|
|
78
78
|
"scope",
|
|
79
|
+
# A delegated pillar's condition is stored in `pillars` like any
|
|
80
|
+
# other, but this tool did not measure it and cannot see when the
|
|
81
|
+
# producer's scoring changed. `secure-code-agent` altered its
|
|
82
|
+
# normalizer twice in one day without touching the document's
|
|
83
|
+
# schema: same shape, same fields, a different number for the same
|
|
84
|
+
# repository. Without this field the series joins straight across
|
|
85
|
+
# that -- this module's opening defect, arriving through a tool we
|
|
86
|
+
# delegated to rather than one we run.
|
|
87
|
+
"delegated_producers",
|
|
79
88
|
)
|
|
80
89
|
|
|
81
90
|
|
|
@@ -131,6 +140,11 @@ class ScanRecord:
|
|
|
131
140
|
aspects: dict[str, float | None] = field(default_factory=dict)
|
|
132
141
|
pillars: dict[str, float | None] = field(default_factory=dict)
|
|
133
142
|
practice_level: int | None = None
|
|
143
|
+
#: Each delegated pillar's producer and version, as the document
|
|
144
|
+
#: announced them -- `("security:secure-code-agent 0.9.0",)`. A
|
|
145
|
+
#: comparability field: the producer's version is the only thing
|
|
146
|
+
#: this tool can see that moves when their scoring does.
|
|
147
|
+
delegated_producers: tuple[str, ...] = ()
|
|
134
148
|
evidence_status: str = ""
|
|
135
149
|
# Which of those a remediation prompt actually asked somebody to fix.
|
|
136
150
|
# This is what makes recurrence a strong signal rather than a weak
|
|
@@ -210,6 +224,7 @@ FIELD_NAMES: dict[str, str] = {
|
|
|
210
224
|
"analyzers": "which analyzers contributed",
|
|
211
225
|
"scored_languages": "the scored languages",
|
|
212
226
|
"scope": "the scan scope",
|
|
227
|
+
"delegated_producers": "a delegated pillar's producer or its version",
|
|
213
228
|
}
|
|
214
229
|
|
|
215
230
|
|
|
@@ -349,11 +364,31 @@ def _is_scan_record(line: str) -> bool:
|
|
|
349
364
|
return isinstance(record, dict) and "recorded_at" in record
|
|
350
365
|
|
|
351
366
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
367
|
+
def _sequence_fields() -> tuple[str, ...]:
|
|
368
|
+
"""Fields stored as sequences, read off the dataclass.
|
|
369
|
+
|
|
370
|
+
The constant this replaced carried the comment "derived from the
|
|
371
|
+
dataclass rather than listed by hand so a new one is handled the day
|
|
372
|
+
it is added" — and was a hand-written list of five names. The
|
|
373
|
+
sentence was aspirational, and it cost exactly what it promised to
|
|
374
|
+
prevent: `delegated_producers` was added and missed, and the
|
|
375
|
+
round-trip guard caught a stored record no longer comparing equal to
|
|
376
|
+
a freshly built one.
|
|
377
|
+
|
|
378
|
+
`from __future__ import annotations` makes every annotation a
|
|
379
|
+
string, so the declared type is matched textually. That is narrow on
|
|
380
|
+
purpose: a field is a sequence here only if it says `tuple`, and a
|
|
381
|
+
field that stores a sequence under some other spelling would need
|
|
382
|
+
naming — which is a smaller trap than a list nobody updates.
|
|
383
|
+
"""
|
|
384
|
+
from dataclasses import fields
|
|
385
|
+
return tuple(
|
|
386
|
+
spec.name for spec in fields(ScanRecord)
|
|
387
|
+
if str(spec.type).startswith("tuple")
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
_SEQUENCE_FIELDS: tuple[str, ...] = _sequence_fields()
|
|
357
392
|
|
|
358
393
|
|
|
359
394
|
def read_history(path: Path) -> list[ScanRecord]:
|
|
@@ -465,6 +500,59 @@ def _score_fields(score: dict[str, Any]) -> dict[str, Any]:
|
|
|
465
500
|
}
|
|
466
501
|
|
|
467
502
|
|
|
503
|
+
def _delegated_producers(report: dict[str, Any]) -> tuple[str, ...]:
|
|
504
|
+
"""Which tool, at which version, supplied each delegated pillar.
|
|
505
|
+
|
|
506
|
+
Sorted and joined into one string per pillar so the comparability
|
|
507
|
+
key stays a plain value. Only delegated pillars carry a producer;
|
|
508
|
+
the ones this tool measures itself are already covered by
|
|
509
|
+
`rubric_version`.
|
|
510
|
+
|
|
511
|
+
A pillar whose document names no version still contributes its
|
|
512
|
+
producer, because "the same tool, version unknown" and "a different
|
|
513
|
+
tool" are different facts and only the second should break a series
|
|
514
|
+
on its own.
|
|
515
|
+
"""
|
|
516
|
+
found = []
|
|
517
|
+
for entry in report.get("pillars") or []:
|
|
518
|
+
tool = entry.get("delegated_to")
|
|
519
|
+
if not tool:
|
|
520
|
+
continue
|
|
521
|
+
found.append(f"{entry.get('pillar')}:{tool} {_model_key(entry)}")
|
|
522
|
+
return tuple(sorted(found))
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _model_key(entry: dict[str, Any]) -> str:
|
|
526
|
+
"""What identifies the instrument behind one delegated pillar.
|
|
527
|
+
|
|
528
|
+
The producer's **scoring model** when the document declares one, and
|
|
529
|
+
its version otherwise.
|
|
530
|
+
|
|
531
|
+
Version was the first answer and it over-breaks: a docs-only release
|
|
532
|
+
of the producer opens a new series having changed no score, and a
|
|
533
|
+
break that fires for nothing teaches a reader to ignore the ones that
|
|
534
|
+
matter. `scoring_model` is the producer's own identifier for "a
|
|
535
|
+
repository's condition could differ here for reasons that are not the
|
|
536
|
+
repository", so it moves when the numbers can and not otherwise.
|
|
537
|
+
|
|
538
|
+
**A document without the field is never collapsed to a single model.**
|
|
539
|
+
Falling back to the version fragments those releases, which is
|
|
540
|
+
correct: `secure-code-agent`'s releases before the field existed do
|
|
541
|
+
not share one scoring model — the corroboration merge, the rank
|
|
542
|
+
discount and the normalizer each moved the numbers independently.
|
|
543
|
+
Assigning them one id would assert a comparability that was never
|
|
544
|
+
true, so the producer reserves model 1 and never emits it.
|
|
545
|
+
|
|
546
|
+
The v1-to-v2 transition therefore breaks the series once, where the
|
|
547
|
+
instrument's self-description changed. That break is honest and it
|
|
548
|
+
happens exactly once.
|
|
549
|
+
"""
|
|
550
|
+
model = entry.get("scoring_model")
|
|
551
|
+
if model is not None:
|
|
552
|
+
return f"model {model}"
|
|
553
|
+
return str(entry.get("producer_version") or "unversioned")
|
|
554
|
+
|
|
555
|
+
|
|
468
556
|
def record_of(report: dict[str, Any], config: dict[str, Any], version: str,
|
|
469
557
|
calibration: float, fingerprints: tuple[str, ...],
|
|
470
558
|
targeted: tuple[str, ...] = (), backfilled: bool = False) -> ScanRecord:
|
|
@@ -496,6 +584,7 @@ def record_of(report: dict[str, Any], config: dict[str, Any], version: str,
|
|
|
496
584
|
# opts in by setting one field rather than by threading a new
|
|
497
585
|
# argument through every caller of this function.
|
|
498
586
|
transformation=str(report.get("transformation") or ""),
|
|
587
|
+
delegated_producers=_delegated_producers(report),
|
|
499
588
|
estimate=published["estimate"],
|
|
500
589
|
range_low=published["range_low"],
|
|
501
590
|
range_high=published["range_high"],
|
|
@@ -27,8 +27,10 @@ import shlex
|
|
|
27
27
|
from pathlib import Path
|
|
28
28
|
from typing import Any
|
|
29
29
|
|
|
30
|
+
from ._catalog import MAX_TIMEOUT_SECONDS, MIN_TIMEOUT_SECONDS
|
|
30
31
|
from ._operator_reads import PathNotAllowed, read_source_file
|
|
31
32
|
from ._runner import Invocation, run
|
|
33
|
+
from ._user_config import user_config_answers
|
|
32
34
|
from ._xml import AnalyzerXmlRefused, parse_analyzer_xml
|
|
33
35
|
|
|
34
36
|
_ENV_ASSIGNMENT = re.compile(r"^[A-Za-z_]\w*=")
|
|
@@ -67,23 +69,67 @@ def _parse_command(command: list[str]) -> tuple[dict[str, str], list[str]]:
|
|
|
67
69
|
def suite_opted_in(config: dict[str, Any]) -> bool:
|
|
68
70
|
"""Both halves of the opt-in: the request, and a command to run.
|
|
69
71
|
|
|
70
|
-
A pure function of the config it is handed, deliberately.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
72
|
+
A pure function of the config it is handed, deliberately.
|
|
73
|
+
`load_config` strips **`test_execution.requested`** out of the
|
|
74
|
+
repository tier before the merge (D147), so the *request* reaching
|
|
75
|
+
here came from the person.
|
|
76
|
+
|
|
77
|
+
`expected_commands.test` is **not** stripped, and this docstring
|
|
78
|
+
used to say it was. That key is documentation — "this is how you
|
|
79
|
+
test me" — and `require_test_command` reads it, so removing it would
|
|
80
|
+
fail a real gate on every repository that correctly documents its
|
|
81
|
+
command. The consequence is that the command in a merged config may
|
|
82
|
+
be the tree's, which makes this function the wrong place to learn
|
|
83
|
+
*what* to run. It answers only whether a run was consented to and
|
|
84
|
+
whether any command is documented; `opted_in_command` answers which
|
|
85
|
+
program the person actually agreed to.
|
|
81
86
|
"""
|
|
82
87
|
requested = bool((config.get("test_execution") or {}).get("requested"))
|
|
83
88
|
command = (config.get("expected_commands") or {}).get("test")
|
|
84
89
|
return requested and bool(command)
|
|
85
90
|
|
|
86
91
|
|
|
92
|
+
def opted_in_command() -> list[str]:
|
|
93
|
+
"""The test command the person consented to — user tier only.
|
|
94
|
+
|
|
95
|
+
Cited by `config._host_authority_stripped` and by `_setup_persist`
|
|
96
|
+
as the reader that makes keeping `expected_commands.test` in the
|
|
97
|
+
repository document safe. **It did not exist.** Three docstrings
|
|
98
|
+
described a boundary that nothing enforced, and `run_test_suite`
|
|
99
|
+
read the merged config instead — so a repository that documented a
|
|
100
|
+
command had that command executed the moment any person opted in,
|
|
101
|
+
which is the authority inversion D147 named and did not close.
|
|
102
|
+
|
|
103
|
+
The distinction it restores: the tree may *say* how it is tested;
|
|
104
|
+
only the person may choose what this host runs. Setup writes the
|
|
105
|
+
command into the user tier at the moment it is shown and accepted
|
|
106
|
+
(`_setup_persist`), so consent and program are recorded together.
|
|
107
|
+
|
|
108
|
+
Empty means the person opted in without a command reaching their
|
|
109
|
+
tier, which is not permission to fall back to the repository's.
|
|
110
|
+
"""
|
|
111
|
+
answers = user_config_answers() or {}
|
|
112
|
+
command = (answers.get("expected_commands") or {}).get("test")
|
|
113
|
+
return list(command) if isinstance(command, list) else []
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _bounded_suite_timeout(configured: Any) -> int:
|
|
117
|
+
"""A suite timeout the audited tree cannot weaponise.
|
|
118
|
+
|
|
119
|
+
The same clamp `_catalog._bounded_timeout` applies to
|
|
120
|
+
`analyzers.timeout_seconds`, on the sibling door: a crafted
|
|
121
|
+
`2147483647` makes the host wait sixty-eight years for a child it
|
|
122
|
+
was told to run, which is D40's family in a field that was missing
|
|
123
|
+
the bound. A non-integer or out-of-band value falls back to the
|
|
124
|
+
default rather than propagating.
|
|
125
|
+
"""
|
|
126
|
+
try:
|
|
127
|
+
seconds = int(configured)
|
|
128
|
+
except (TypeError, ValueError):
|
|
129
|
+
return DEFAULT_SUITE_TIMEOUT_SECONDS
|
|
130
|
+
return max(MIN_TIMEOUT_SECONDS, min(seconds, MAX_TIMEOUT_SECONDS))
|
|
131
|
+
|
|
132
|
+
|
|
87
133
|
def run_test_suite(root: Path, config: dict[str, Any]) -> dict[str, Any] | None:
|
|
88
134
|
"""Execute the configured test command, or ``None`` when not opted in.
|
|
89
135
|
|
|
@@ -92,14 +138,34 @@ def run_test_suite(root: Path, config: dict[str, Any]) -> dict[str, Any] | None:
|
|
|
92
138
|
"""
|
|
93
139
|
if not suite_opted_in(config):
|
|
94
140
|
return None
|
|
95
|
-
|
|
141
|
+
# Every spawn setting is read from the **person's** tier, never from
|
|
142
|
+
# the merged config. `config["expected_commands"]["test"]` is what
|
|
143
|
+
# this line used to be, and the repository's document reaches that
|
|
144
|
+
# key by design (it is documentation `require_test_command` needs),
|
|
145
|
+
# so reading it here let the tree choose the program the moment
|
|
146
|
+
# anybody opted in. The keys are named here rather than hidden
|
|
147
|
+
# behind the helper so a reader sees which population is governed.
|
|
148
|
+
answers = user_config_answers() or {}
|
|
149
|
+
command = opted_in_command()
|
|
150
|
+
if not command:
|
|
151
|
+
return {
|
|
152
|
+
"command": (config.get("expected_commands") or {}).get("test") or [],
|
|
153
|
+
"ran": False, "exit_code": None, "passed": False,
|
|
154
|
+
"detail": (
|
|
155
|
+
"opted in, but no test command is recorded in the user tier; "
|
|
156
|
+
"the repository's documented command is not run on its own say-so"
|
|
157
|
+
),
|
|
158
|
+
"coverage_percent": None,
|
|
159
|
+
}
|
|
96
160
|
env, argv = _parse_command(command)
|
|
97
161
|
# A whole test suite is legitimately slower than a single analyzer, so
|
|
98
162
|
# it gets its own timeout rather than the 120s analyzer cap that was
|
|
99
163
|
# killing real suites mid-run. Operator-configurable under
|
|
100
|
-
# `test_execution.timeout_seconds
|
|
101
|
-
timeout
|
|
102
|
-
|
|
164
|
+
# `test_execution.timeout_seconds`, and clamped: a repository naming
|
|
165
|
+
# a timeout is naming how long this host waits.
|
|
166
|
+
timeout = _bounded_suite_timeout(
|
|
167
|
+
(answers.get("test_execution") or {}).get(
|
|
168
|
+
"timeout_seconds", DEFAULT_SUITE_TIMEOUT_SECONDS))
|
|
103
169
|
if not argv:
|
|
104
170
|
# A command that is only env assignments (or empty) names no
|
|
105
171
|
# program to run. Report it as configured but unrunnable rather
|