maintainability-agent 2.7.0__tar.gz → 2.8.1__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.7.0/src/maintainability_agent.egg-info → maintainability_agent-2.8.1}/PKG-INFO +10 -10
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/README.md +9 -9
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/pyproject.toml +1 -1
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1/src/maintainability_agent.egg-info}/PKG-INFO +10 -10
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_agent.egg-info/SOURCES.txt +6 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/__init__.py +1 -1
- maintainability_agent-2.8.1/src/maintainability_audit/_anchor.py +49 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_assets/standard.md +16 -4
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_cognitive.py +16 -6
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_evidence_view.py +30 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_html_view.py +11 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_masking.py +44 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_ranges_cobol.py +15 -5
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_ranges_swift.py +6 -1
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_safe_write.py +46 -1
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_semantic_ts.py +32 -16
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/baseline.py +5 -1
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/config.py +67 -2
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/prompts.py +4 -3
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/renderers.py +1 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/scoring.py +5 -1
- maintainability_agent-2.8.1/tests/test_anchor_and_mode.py +139 -0
- maintainability_agent-2.8.1/tests/test_anchor_disclosure.py +176 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_authorship_gates.py +36 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_direct_runner_availability_class.py +15 -7
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_evidence_properties.py +3 -1
- maintainability_agent-2.8.1/tests/test_operator_named_paths.py +113 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_platform_claim.py +80 -5
- maintainability_agent-2.8.1/tests/test_scanner_unsafe_direction.py +147 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_stage8_contract.py +15 -7
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_three_presentations.py +3 -1
- maintainability_agent-2.8.1/tests/test_tree_chosen_spawn.py +220 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_verified_grade.py +5 -4
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/LICENSE +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/setup.cfg +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_agent.egg-info/dependency_links.txt +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_agent.egg-info/entry_points.txt +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_agent.egg-info/requires.txt +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_agent.egg-info/top_level.txt +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/__main__.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_adapters.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_analysis.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_arguments.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_aspects.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_assets/analyzer-catalog.json +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_attestation.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_backfill.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_bands.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_built_ins.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_calibration.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_catalog.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_charts.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_config_defaults.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_conformance.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_corroborate.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_coverage_notes.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_derive.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_discovery.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_documents.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_economics.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_economics_view.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_environment.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_finding_match.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_first_run.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_formula.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_gates.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_generic.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_grant_ledger.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_history_view.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_hostile_prompt.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_hotspots.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_html_report_sections.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_identity.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_jvm_adapters.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_markdown_sections.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_mcp_audit.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_mcp_gate.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_mcp_grants.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_mcp_refusals.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_mcp_resources.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_mcp_setup.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_metric_adapters.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_metrics_types.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_pillars.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_practice.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_pressures.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_ranges_c.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_ranges_core.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_ranges_cpp.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_ranges_csharp.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_ranges_fortran.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_ranges_java.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_ranges_js.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_ratchet.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_recurrence.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_run_comparison.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_runner.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_scan_history.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_scan_view.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_second_source.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_selection.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_semantic.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_semantic_policy.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_semantic_view.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_setup_errors.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_setup_persist.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_skill_data/SKILL.md +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_skill_data/agents/anthropic.yaml +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_skill_data/agents/copilot.yaml +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_skill_data/agents/openai.yaml +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_skill_data/copilot/maintainability-agent.prompt.md +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_skill_data/references/finding-taxonomy.md +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_skill_install.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_stored_grants.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_tdd_view.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_test_commands.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_test_execution.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_test_pairing.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_tokens.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_tool_adapters.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_trends.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_user_config.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_verdict_adapters.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_verification.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_work_order.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_work_order_view.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_work_order_weights.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_xml.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/cli.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/deadcode.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/declarations.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/duplication.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/evidence.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/git_tools.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/history.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/idioms.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/instructions.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/mcp_server.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/metrics.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/report.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/sarif.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/similarity.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/source.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_acquisition_trust.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_adapter_docs.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_adapter_recordings.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_adapters.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_added_lines.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_analysis_coverage.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_analysis_execution.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_analyzer_bridge.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_analyzer_config_isolation.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_analyzer_estimate_claims.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_analyzer_pinning.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_analyzer_primary_estimate.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_analyzer_provenance.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_analyzer_provenance_exclusions.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_analyzer_scoring_path.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_analyzer_spread_range.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_analyzer_xml_bounds.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_annotations_match_behaviour.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_anticipated_refusals.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_architecture.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_artifact_write_route_class.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_aspect_coverage.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_asset_snapshot_class.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_ast_reading_resolves_spellings.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_attestation.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_audit_components.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_authorization_freshness.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_backfill.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_band_concealment_class.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_band_concealment_p3.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_band_pressures.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_bands.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_body_or_signature_class.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_c_declarations.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_calibration_corpus.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_calibration_drift.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_calibration_tooling.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_catalog_selection.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_catalog_tier_promise.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_chat_primary_docs.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_checkstyle_adapter.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_checkstyle_catalog.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_ci_history_recipe.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_ci_installs_the_analyzer_pool.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_claimed_languages.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_cli.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_clone_group_class.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_cobol_declarations.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_cognitive_complexity.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_cohort_statistics.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_concealment_properties.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_config_shape.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_consent_and_grant.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_consumer_migration.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_contract_claims.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_corroborate.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_coverage_by_language.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_cpp_declarations.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_csharp_declarations.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_d15_composition.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_d15_goal_directed.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_dead_code.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_declaration_edge_cases.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_declaration_grading.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_declaration_ranges.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_declaration_source.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_declared_authorship.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_declared_imports.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_derivation_mix.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_determinism.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_dimension_ratchet.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_discovery.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_doc_claims.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_docs_links.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_economic_context.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_empty_output_not_clean.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_empty_output_not_clean_adapters.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_environment_work_order.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_evidence_invariants.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_evidence_normalization.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_exclusion_dialects.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_falsifier_standard.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_file_list_cap_class.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_finding_identity.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_first_run_elicitation.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_first_run_prompt.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_first_run_reelicitation.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_fix_breadth_window.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_fixed_form_fortran.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_format_ask.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_fortran_declarations.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_fortran_metrics.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_fortran_practice.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_git_argv.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_git_read_only.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_git_readonly_class.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_grant_only_user_tier.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_history.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_history_boundary.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_history_manifest.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_history_schema2.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_history_window.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_hostile_prompt.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_html_executive.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_html_section_parity_class.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_identity_docs.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_identity_resolution.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_idiom_divergence.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_installed_wheel_runtime.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_instruction_targets.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_java_range_guard.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_java_ranges.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_java_scored.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_java_wiring.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_js_complexity_operators.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_js_declarations.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_language_coverage.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_mcp_baseline_payload.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_mcp_history.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_mcp_server.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_migration_1_0.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_near_duplicates.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_network_disclosure.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_one_setup_across_surfaces_class.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_pattern_budget.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_phase6_claims.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_pillars_and_practice.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_pmd_adapter.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_pmd_audit_closures.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_pool_runs_by_config.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_population_floors.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_practice_nested_gates_class.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_presentation_refusals.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_promises.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_prompt_escalation_once.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_prompt_honesty.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_prompt_location_escalation_class.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_prove_falsifiers.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_python_declarations.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_read_boundary.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_readme_claims.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_recurrence.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_release_plan.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_release_plan_status.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_report_remedy_copy.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_roles_recorded.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_root_grants.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_rubric_ignores_project_lint_config.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_run_comparison.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_runner.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_sarif.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_scan_history.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_scan_scope.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_scanning.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_schema_fidelity.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_scope_conformance.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_scoring_calibration.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_semantic_policy.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_setup_gate_completeness.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_setup_gate_independent_contract.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_setup_precondition.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_skill_install.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_source_index.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_spotbugs_adapter.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_spotbugs_catalog.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_suite_isolation.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_swift_declarations.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_tdd_structure.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_test_commands.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_test_execution_class.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_trends.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_type_fields_not_functions_class.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_uat_falsifiers.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_unread_code.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_user_config_tier.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_verification_audit_closures.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_version_claim.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_wheel_contents.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_work_order.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_work_order_items.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_work_order_selection.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_workflow_supply_chain.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_write_boundary.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/tests/test_write_route_class.py +0 -0
- {maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/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.8.1
|
|
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.8.1**.
|
|
48
48
|
|
|
49
49
|
**Languages parsed:** Python, Java, C, C++, C#, Swift, Fortran (free-form
|
|
50
50
|
*and* fixed-form), and the JS/TS family — each by a scanner written for it, and
|
|
@@ -383,8 +383,8 @@ Based on ISO/IEC 25010 maintainability — modularity, reusability,
|
|
|
383
383
|
analyzability, modifiability, testability. Scores are **rates calibrated
|
|
384
384
|
against real code**, not counts: every pressure is normalized against the
|
|
385
385
|
median a pinned 112-repo corpus of mature projects actually carries —
|
|
386
|
-
|
|
387
|
-
ghidra, LAPACK, …). How that corpus was selected, what each language actually
|
|
386
|
+
eight of the ten parsed languages (django, angular, spring-framework,
|
|
387
|
+
ghidra, LAPACK, …); Swift and COBOL are parsed but unanchored. How that corpus was selected, what each language actually
|
|
388
388
|
reads, and what the numbers do *not* establish:
|
|
389
389
|
[the calibration study](docs/calibration-2.0-study.md), so `2.5x` means
|
|
390
390
|
"two and a half times what well-maintained real code shows." The corpus median
|
|
@@ -430,12 +430,12 @@ and costs roughly a grade — use `fetch-depth: 0`.)
|
|
|
430
430
|
|
|
431
431
|
## Platform support
|
|
432
432
|
|
|
433
|
-
**POSIX.
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
433
|
+
**POSIX. Windows is not claimed, for a measured reason.** A `windows-latest` probe
|
|
434
|
+
found most failures in two POSIX-only calls, `os.fchmod` and `os.O_DIRECTORY`. The
|
|
435
|
+
bounded, symlink-refusing write works through a *file descriptor*, so a validated
|
|
436
|
+
path cannot be swapped for a symlink before the write lands — Windows has no
|
|
437
|
+
equivalent and the portable rewrite is the hole. **An unsupported platform never
|
|
438
|
+
buys green by weakening the supported ones.**
|
|
439
439
|
|
|
440
440
|
## Invokable skill / slash command
|
|
441
441
|
|
|
@@ -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.8.1**.
|
|
9
9
|
|
|
10
10
|
**Languages parsed:** Python, Java, C, C++, C#, Swift, Fortran (free-form
|
|
11
11
|
*and* fixed-form), and the JS/TS family — each by a scanner written for it, and
|
|
@@ -344,8 +344,8 @@ Based on ISO/IEC 25010 maintainability — modularity, reusability,
|
|
|
344
344
|
analyzability, modifiability, testability. Scores are **rates calibrated
|
|
345
345
|
against real code**, not counts: every pressure is normalized against the
|
|
346
346
|
median a pinned 112-repo corpus of mature projects actually carries —
|
|
347
|
-
|
|
348
|
-
ghidra, LAPACK, …). How that corpus was selected, what each language actually
|
|
347
|
+
eight of the ten parsed languages (django, angular, spring-framework,
|
|
348
|
+
ghidra, LAPACK, …); Swift and COBOL are parsed but unanchored. How that corpus was selected, what each language actually
|
|
349
349
|
reads, and what the numbers do *not* establish:
|
|
350
350
|
[the calibration study](docs/calibration-2.0-study.md), so `2.5x` means
|
|
351
351
|
"two and a half times what well-maintained real code shows." The corpus median
|
|
@@ -391,12 +391,12 @@ and costs roughly a grade — use `fetch-depth: 0`.)
|
|
|
391
391
|
|
|
392
392
|
## Platform support
|
|
393
393
|
|
|
394
|
-
**POSIX.
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
394
|
+
**POSIX. Windows is not claimed, for a measured reason.** A `windows-latest` probe
|
|
395
|
+
found most failures in two POSIX-only calls, `os.fchmod` and `os.O_DIRECTORY`. The
|
|
396
|
+
bounded, symlink-refusing write works through a *file descriptor*, so a validated
|
|
397
|
+
path cannot be swapped for a symlink before the write lands — Windows has no
|
|
398
|
+
equivalent and the portable rewrite is the hole. **An unsupported platform never
|
|
399
|
+
buys green by weakening the supported ones.**
|
|
400
400
|
|
|
401
401
|
## Invokable skill / slash command
|
|
402
402
|
|
|
@@ -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.8.1"
|
|
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.8.1
|
|
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.8.1**.
|
|
48
48
|
|
|
49
49
|
**Languages parsed:** Python, Java, C, C++, C#, Swift, Fortran (free-form
|
|
50
50
|
*and* fixed-form), and the JS/TS family — each by a scanner written for it, and
|
|
@@ -383,8 +383,8 @@ Based on ISO/IEC 25010 maintainability — modularity, reusability,
|
|
|
383
383
|
analyzability, modifiability, testability. Scores are **rates calibrated
|
|
384
384
|
against real code**, not counts: every pressure is normalized against the
|
|
385
385
|
median a pinned 112-repo corpus of mature projects actually carries —
|
|
386
|
-
|
|
387
|
-
ghidra, LAPACK, …). How that corpus was selected, what each language actually
|
|
386
|
+
eight of the ten parsed languages (django, angular, spring-framework,
|
|
387
|
+
ghidra, LAPACK, …); Swift and COBOL are parsed but unanchored. How that corpus was selected, what each language actually
|
|
388
388
|
reads, and what the numbers do *not* establish:
|
|
389
389
|
[the calibration study](docs/calibration-2.0-study.md), so `2.5x` means
|
|
390
390
|
"two and a half times what well-maintained real code shows." The corpus median
|
|
@@ -430,12 +430,12 @@ and costs roughly a grade — use `fetch-depth: 0`.)
|
|
|
430
430
|
|
|
431
431
|
## Platform support
|
|
432
432
|
|
|
433
|
-
**POSIX.
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
433
|
+
**POSIX. Windows is not claimed, for a measured reason.** A `windows-latest` probe
|
|
434
|
+
found most failures in two POSIX-only calls, `os.fchmod` and `os.O_DIRECTORY`. The
|
|
435
|
+
bounded, symlink-refusing write works through a *file descriptor*, so a validated
|
|
436
|
+
path cannot be swapped for a symlink before the write lands — Windows has no
|
|
437
|
+
equivalent and the portable rewrite is the hole. **An unsupported platform never
|
|
438
|
+
buys green by weakening the supported ones.**
|
|
439
439
|
|
|
440
440
|
## Invokable skill / slash command
|
|
441
441
|
|
|
@@ -11,6 +11,7 @@ src/maintainability_audit/__init__.py
|
|
|
11
11
|
src/maintainability_audit/__main__.py
|
|
12
12
|
src/maintainability_audit/_adapters.py
|
|
13
13
|
src/maintainability_audit/_analysis.py
|
|
14
|
+
src/maintainability_audit/_anchor.py
|
|
14
15
|
src/maintainability_audit/_arguments.py
|
|
15
16
|
src/maintainability_audit/_aspects.py
|
|
16
17
|
src/maintainability_audit/_attestation.py
|
|
@@ -143,6 +144,8 @@ tests/test_analyzer_provenance_exclusions.py
|
|
|
143
144
|
tests/test_analyzer_scoring_path.py
|
|
144
145
|
tests/test_analyzer_spread_range.py
|
|
145
146
|
tests/test_analyzer_xml_bounds.py
|
|
147
|
+
tests/test_anchor_and_mode.py
|
|
148
|
+
tests/test_anchor_disclosure.py
|
|
146
149
|
tests/test_annotations_match_behaviour.py
|
|
147
150
|
tests/test_anticipated_refusals.py
|
|
148
151
|
tests/test_architecture.py
|
|
@@ -253,6 +256,7 @@ tests/test_migration_1_0.py
|
|
|
253
256
|
tests/test_near_duplicates.py
|
|
254
257
|
tests/test_network_disclosure.py
|
|
255
258
|
tests/test_one_setup_across_surfaces_class.py
|
|
259
|
+
tests/test_operator_named_paths.py
|
|
256
260
|
tests/test_pattern_budget.py
|
|
257
261
|
tests/test_phase6_claims.py
|
|
258
262
|
tests/test_pillars_and_practice.py
|
|
@@ -283,6 +287,7 @@ tests/test_runner.py
|
|
|
283
287
|
tests/test_sarif.py
|
|
284
288
|
tests/test_scan_history.py
|
|
285
289
|
tests/test_scan_scope.py
|
|
290
|
+
tests/test_scanner_unsafe_direction.py
|
|
286
291
|
tests/test_scanning.py
|
|
287
292
|
tests/test_schema_fidelity.py
|
|
288
293
|
tests/test_scope_conformance.py
|
|
@@ -302,6 +307,7 @@ tests/test_tdd_structure.py
|
|
|
302
307
|
tests/test_test_commands.py
|
|
303
308
|
tests/test_test_execution_class.py
|
|
304
309
|
tests/test_three_presentations.py
|
|
310
|
+
tests/test_tree_chosen_spawn.py
|
|
305
311
|
tests/test_trends.py
|
|
306
312
|
tests/test_type_fields_not_functions_class.py
|
|
307
313
|
tests/test_uat_falsifiers.py
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""What the reference corpus does not hold, and how to say it.
|
|
2
|
+
|
|
3
|
+
Rubric data, like `_calibration`: a fact about the anchor rather than a
|
|
4
|
+
computation over a repository. It lives beside the constant it qualifies
|
|
5
|
+
because the two go stale together — a recalibration that adds Swift and
|
|
6
|
+
COBOL to the corpus empties this in the same commit that moves
|
|
7
|
+
`CALIBRATION_C`.
|
|
8
|
+
|
|
9
|
+
**Unanchored is policy, not an oversight.** Under the corpus policy
|
|
10
|
+
decided with 2.4.1, a language ships parsed and the corpus is re-measured
|
|
11
|
+
once after the remaining scanners land: adding a scanner moves
|
|
12
|
+
`scanner_fingerprint` and invalidates every stored measurement, so paying
|
|
13
|
+
per language means paying in full, repeatedly, for an anchor that is
|
|
14
|
+
obsolete again at the next one.
|
|
15
|
+
|
|
16
|
+
What is *not* policy is silence about it where the grade is read. The
|
|
17
|
+
report carried `score.reference` and the note said the corpus spanned
|
|
18
|
+
"every language this scanner parses" — which was true when it was written
|
|
19
|
+
and false from 2.4.0 onward, for three releases. A Grok audit on
|
|
20
|
+
2026-09-04 named it as the second instance of a shape this project had
|
|
21
|
+
already shipped once: a limit disclosed only in JSON is disclosed nowhere
|
|
22
|
+
that matters.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
#: Parsed by a scanner, absent from the reference corpus.
|
|
27
|
+
#:
|
|
28
|
+
#: Stated rather than derived. `tools/calibration/corpus.json` is a
|
|
29
|
+
#: repository file and does not ship in the wheel, so a runtime read would
|
|
30
|
+
#: be unavailable exactly where a user reads a grade.
|
|
31
|
+
#: `test_anchor_disclosure` recomputes `parsed - corpus` from both sources
|
|
32
|
+
#: and fails if this stops matching, so the list cannot quietly go stale.
|
|
33
|
+
UNANCHORED_LANGUAGES: tuple[str, ...] = ("Swift", "COBOL")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def unanchored_names() -> str:
|
|
37
|
+
"""The languages, joined for a sentence. Empty when there are none."""
|
|
38
|
+
return " and ".join(UNANCHORED_LANGUAGES)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def unanchored_sentence() -> str:
|
|
42
|
+
"""One sentence naming what the anchor omits, for the corpus note."""
|
|
43
|
+
if not UNANCHORED_LANGUAGES:
|
|
44
|
+
return ""
|
|
45
|
+
return (
|
|
46
|
+
f"{unanchored_names()} are parsed but absent from it, so their "
|
|
47
|
+
"findings are as good as their parser while their grade is "
|
|
48
|
+
"provisional."
|
|
49
|
+
)
|
|
@@ -132,10 +132,22 @@ about LAPACK relative to mature OSS *web* code, and not a statement about
|
|
|
132
132
|
typical Fortran, because no typical Fortran was in the comparison set.
|
|
133
133
|
|
|
134
134
|
**2.0.0 extended the corpus rather than the caveat.** It is now 112
|
|
135
|
-
repositories spanning
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
repositories spanning eight languages: Python, TypeScript, JavaScript,
|
|
136
|
+
Java, C, C++, C# and Fortran. The original 40 rows are unchanged and still
|
|
137
|
+
pinned to the commits they were measured at, so what moved the constants
|
|
138
|
+
is the languages added and nothing else.
|
|
139
|
+
|
|
140
|
+
**Two parsed languages are not in it.** Swift (2.4.0) and COBOL (2.7.0)
|
|
141
|
+
shipped after that measurement and are **unanchored**: a scanner reads
|
|
142
|
+
them, and the medians their rates are compared against were measured on
|
|
143
|
+
other languages. This is the corpus policy decided with 2.4.1 — a
|
|
144
|
+
language ships parsed, and the corpus is re-measured once after the
|
|
145
|
+
remaining scanners land, because a new scanner moves
|
|
146
|
+
`scanner_fingerprint` and invalidates every stored row. The cost is
|
|
147
|
+
disclosed rather than absorbed: findings in those languages are as good
|
|
148
|
+
as their parser, their grade is provisional, and every surface that
|
|
149
|
+
prints a grade says so. This sentence read "every language this scanner
|
|
150
|
+
parses" for three releases after it stopped being true.
|
|
139
151
|
|
|
140
152
|
Three limits remain, and they are narrower than the one they replace:
|
|
141
153
|
|
{maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_cognitive.py
RENAMED
|
@@ -35,11 +35,11 @@ import re
|
|
|
35
35
|
|
|
36
36
|
from ._masking import mask_fortran_lines, mask_lines
|
|
37
37
|
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
|
|
38
|
+
# No COBOL masker is imported here, deliberately: `cobol_cognitive` takes
|
|
39
|
+
# lines that are already masked. COBOL's mask is the only file-level one —
|
|
40
|
+
# what a line means depends on which division it sits in — so re-masking
|
|
41
|
+
# the paragraph slice `detect_functions` hands over blanked it entirely
|
|
42
|
+
# and scored every COBOL paragraph 0. See `cobol_cognitive`.
|
|
43
43
|
|
|
44
44
|
# Constructs that both cost a point and deepen the nesting for whatever
|
|
45
45
|
# they contain.
|
|
@@ -207,8 +207,18 @@ def cobol_cognitive(lines: list[str]) -> int:
|
|
|
207
207
|
a call and opens nothing; `PERFORM UNTIL DONE` is a loop and does.
|
|
208
208
|
Counting every `PERFORM` as nesting would make an ordinary
|
|
209
209
|
call-per-line paragraph read as deeply nested.
|
|
210
|
+
|
|
211
|
+
**Takes lines that are already masked, and does not mask them again.**
|
|
212
|
+
Every other reader here may re-mask freely because its masker is
|
|
213
|
+
line-local; COBOL's is the only file-level one, since deciding what a
|
|
214
|
+
line means requires knowing which division it sits in. `detect_functions`
|
|
215
|
+
hands this a *slice* of the masked file, and a paragraph slice contains
|
|
216
|
+
no `PROCEDURE DIVISION` — so re-masking blanked the whole thing and
|
|
217
|
+
every COBOL paragraph in production scored exactly 0, including deeply
|
|
218
|
+
nested ones. The souvenir test passed throughout because it called this
|
|
219
|
+
with a whole file. Found by a Grok audit on 2026-09-04.
|
|
210
220
|
"""
|
|
211
|
-
masked =
|
|
221
|
+
masked = lines
|
|
212
222
|
score = 0
|
|
213
223
|
depth = 0
|
|
214
224
|
for line in masked:
|
|
@@ -339,3 +339,33 @@ def test_suite_lines(block: dict[str, Any] | None) -> list[str]:
|
|
|
339
339
|
if detail and not block.get("passed"):
|
|
340
340
|
lines.append(f"Detail: {detail}")
|
|
341
341
|
return lines
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def unanchored_languages(score: dict[str, Any]) -> tuple[str, ...]:
|
|
345
|
+
"""Parsed languages the reference corpus does not hold, from the report.
|
|
346
|
+
|
|
347
|
+
Read off `score.reference` rather than imported from `_anchor`, so a
|
|
348
|
+
report rendered from stored JSON discloses what *that* run's anchor
|
|
349
|
+
omitted rather than what today's build omits.
|
|
350
|
+
"""
|
|
351
|
+
return tuple((score.get("reference") or {}).get("unanchored_languages") or ())
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def unanchored_caveat(score: dict[str, Any]) -> list[str]:
|
|
355
|
+
"""The anchor's gap, in one sentence, for any skin that prints a grade.
|
|
356
|
+
|
|
357
|
+
Lives here because all three skins were about to carry their own copy
|
|
358
|
+
of the same lookup and the same sentence. `score.reference` rides on
|
|
359
|
+
the JSON report, and the JSON is not what most people read — a limit
|
|
360
|
+
disclosed only there is disclosed nowhere that matters, which is the
|
|
361
|
+
shape this project already shipped once as F1.
|
|
362
|
+
"""
|
|
363
|
+
names = unanchored_languages(score)
|
|
364
|
+
if not names:
|
|
365
|
+
return []
|
|
366
|
+
return ["", (
|
|
367
|
+
f"*{' and '.join(names)} are parsed but are not in the reference "
|
|
368
|
+
"corpus, so a grade or multiple reported for code in them is "
|
|
369
|
+
"provisional: the findings are as good as the parser, the rate "
|
|
370
|
+
"they are compared against was measured on other languages.*"
|
|
371
|
+
), ""]
|
{maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_html_view.py
RENAMED
|
@@ -89,6 +89,16 @@ def render_html(report: dict[str, Any], records: list[Any]) -> str:
|
|
|
89
89
|
return "\n".join(parts)
|
|
90
90
|
|
|
91
91
|
|
|
92
|
+
def _unanchored_html(score: dict[str, Any]) -> list[str]:
|
|
93
|
+
"""The anchor's gap, beside the letter rather than only in the JSON."""
|
|
94
|
+
names = view.unanchored_languages(score)
|
|
95
|
+
if not names:
|
|
96
|
+
return []
|
|
97
|
+
return [f"<div class='caveat'>{escape(' and '.join(names))} are parsed but "
|
|
98
|
+
"are not in the reference corpus, so a grade for code in them is "
|
|
99
|
+
"provisional.</div>"]
|
|
100
|
+
|
|
101
|
+
|
|
92
102
|
def _executive_strip(report: dict[str, Any], score: dict[str, Any],
|
|
93
103
|
records: list[Any]) -> list[str]:
|
|
94
104
|
"""The result, the gate, the finding counts, the direction. First."""
|
|
@@ -110,6 +120,7 @@ def _executive_strip(report: dict[str, Any], score: dict[str, Any],
|
|
|
110
120
|
f"Source: {escape(view.estimate_source(score))}</div>",
|
|
111
121
|
f"<div>{escape(view.status_sentence(score, report.get('analyzer_coverage') is not None))}</div>",
|
|
112
122
|
f"<div>{gate}</div>",
|
|
123
|
+
*_unanchored_html(score),
|
|
113
124
|
f"<div>{escape(_direction_sentence(records))}</div>",
|
|
114
125
|
"<table><tr><th>Severity</th><th>Findings</th></tr>",
|
|
115
126
|
*count_rows,
|
{maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_masking.py
RENAMED
|
@@ -254,3 +254,47 @@ def mask_lines(lines: list[str]) -> list[str]:
|
|
|
254
254
|
text, in_block, in_template = _mask_line(line, in_block, in_template)
|
|
255
255
|
masked.append(text)
|
|
256
256
|
return masked
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
#: Swift's multiline literal, in both spellings: `"""` and the raw form
|
|
260
|
+
#: `#"""` … `"""#` (any number of `#`). Matched loosely on purpose — the
|
|
261
|
+
#: delimiter is what matters, not how many hashes surround it.
|
|
262
|
+
_SWIFT_MULTILINE_RE = re.compile(r'#*"""#*')
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def mask_swift_lines(lines: list[str]) -> list[str]:
|
|
266
|
+
"""Swift, with multiline string literals blanked before anything reads it.
|
|
267
|
+
|
|
268
|
+
`mask_lines` is line-local: it blanks `"a"` on the line it appears on
|
|
269
|
+
and knows nothing about a literal that spans lines. Swift's
|
|
270
|
+
triple-quoted block therefore survived it, and every line between the
|
|
271
|
+
delimiters was read as code — so a `func` written inside a
|
|
272
|
+
documentation string came back as a declaration, with a name, a length
|
|
273
|
+
and a complexity nobody wrote.
|
|
274
|
+
|
|
275
|
+
`_ranges_java` discloses exactly this hole for Java text blocks. It is
|
|
276
|
+
disclosed there and closed here rather than copied: Swift's multiline
|
|
277
|
+
strings carry sample code far more often than Java's, and a
|
|
278
|
+
declaration invented out of documentation is the P7 failure — a number
|
|
279
|
+
a reader with the file in front of them would call absurd.
|
|
280
|
+
|
|
281
|
+
The delimiter lines keep their own text up to and after the marker, so
|
|
282
|
+
`let a = \"\"\"` still reads as an assignment. Length is preserved per
|
|
283
|
+
line, as everywhere else here, or every range after the literal would
|
|
284
|
+
shift.
|
|
285
|
+
"""
|
|
286
|
+
masked: list[str] = []
|
|
287
|
+
inside = False
|
|
288
|
+
for line in lines:
|
|
289
|
+
out = []
|
|
290
|
+
position = 0
|
|
291
|
+
for match in _SWIFT_MULTILINE_RE.finditer(line):
|
|
292
|
+
segment = line[position:match.start()]
|
|
293
|
+
out.append(" " * len(segment) if inside else segment)
|
|
294
|
+
out.append(match.group(0))
|
|
295
|
+
inside = not inside
|
|
296
|
+
position = match.end()
|
|
297
|
+
rest = line[position:]
|
|
298
|
+
out.append(" " * len(rest) if inside else rest)
|
|
299
|
+
masked.append("".join(out))
|
|
300
|
+
return mask_lines(masked)
|
|
@@ -72,11 +72,21 @@ _SECTION_RE = re.compile(r"^\s{0,3}([A-Za-z0-9][A-Za-z0-9-]*)\s+SECTION\s*\.", r
|
|
|
72
72
|
#: paragraph headers in columns 8-11 (Area A) and statements in 12-72
|
|
73
73
|
#: (Area B); after the sequence and indicator columns are stripped, Area A
|
|
74
74
|
#: is offset 0-3 and Area B begins at offset 4 — which is why the bound is
|
|
75
|
-
#: three spaces and not four.
|
|
76
|
-
#:
|
|
77
|
-
#:
|
|
78
|
-
#:
|
|
79
|
-
|
|
75
|
+
#: three spaces and not four. Written `{0,4}` first, and a masked
|
|
76
|
+
#: `DISPLAY "A".` sitting at column 12 came straight back as a declaration
|
|
77
|
+
#: named DISPLAY — one per statement in the file.
|
|
78
|
+
#:
|
|
79
|
+
#: **The period abuts the name**, and that is the load-bearing half. Area A
|
|
80
|
+
#: only separates a header from an *indented* statement; free-form COBOL
|
|
81
|
+
#: writes statements in column 1 too, and once `"A"` is blanked to spaces
|
|
82
|
+
#: `DISPLAY "A".` is a word, a gap, and a period — the exact shape of a
|
|
83
|
+
#: header. The gap is what gives it away, because a real header has
|
|
84
|
+
#: nothing between its name and its period. That rejects the whole class
|
|
85
|
+
#: of operand-bearing statements (`CALL "SUB".`, `MOVE 1 TO X.`) rather
|
|
86
|
+
#: than a list of verbs, which would need a new entry per verb forever.
|
|
87
|
+
#: `_STATEMENT_WORDS` still covers the operand-*less* ones, where no gap
|
|
88
|
+
#: exists to see.
|
|
89
|
+
_PARAGRAPH_RE = re.compile(r"^\s{0,3}([A-Za-z0-9][A-Za-z0-9-]*)\.\s*$")
|
|
80
90
|
_DIVISION_RE = re.compile(r"^\s*[A-Za-z0-9-]+\s+DIVISION\s*\.", re.I)
|
|
81
91
|
_PROCEDURE_RE = re.compile(r"^\s*PROCEDURE\s+DIVISION\b", re.I)
|
|
82
92
|
_END_PROGRAM_RE = re.compile(r"^\s*END\s+PROGRAM\b", re.I)
|
|
@@ -41,6 +41,7 @@ from __future__ import annotations
|
|
|
41
41
|
|
|
42
42
|
import re
|
|
43
43
|
|
|
44
|
+
from ._masking import mask_swift_lines
|
|
44
45
|
from ._metrics_types import DeclRange
|
|
45
46
|
from ._ranges_core import _mask_generics, scan_bounded
|
|
46
47
|
|
|
@@ -160,7 +161,11 @@ def swift_declaration_ranges(lines: list[str]) -> tuple[list[DeclRange], list[st
|
|
|
160
161
|
disabled `#if` arm still counts.
|
|
161
162
|
"""
|
|
162
163
|
ranges, masked = scan_bounded(
|
|
163
|
-
lines, _swift_declaration, descend=("class",), skip_preprocessor=True
|
|
164
|
+
lines, _swift_declaration, descend=("class",), skip_preprocessor=True,
|
|
165
|
+
# Multiline literals are blanked before anything reads a line. The
|
|
166
|
+
# default line-local mask left a `func` inside a `"""` block
|
|
167
|
+
# looking like a declaration — see `mask_swift_lines`.
|
|
168
|
+
mask=mask_swift_lines,
|
|
164
169
|
)
|
|
165
170
|
return _qualify_extension_members(ranges, masked), masked
|
|
166
171
|
|
{maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/_safe_write.py
RENAMED
|
@@ -178,6 +178,30 @@ def _refuse_symlinked_parents(root: Path, target: Path) -> None:
|
|
|
178
178
|
current = current.parent
|
|
179
179
|
|
|
180
180
|
|
|
181
|
+
def _mode_for(target: Path) -> int | None:
|
|
182
|
+
"""The mode a replacement must carry, or `None` to leave it alone.
|
|
183
|
+
|
|
184
|
+
Replacing a file must not change who can read it. `mkstemp` creates
|
|
185
|
+
the staging file 0600, and the code here previously chmod'ed it to a
|
|
186
|
+
fixed 0o644 under a comment claiming to "match the old mode" — so a
|
|
187
|
+
config a user had deliberately restricted came back world-readable
|
|
188
|
+
after the next write, silently, every time.
|
|
189
|
+
|
|
190
|
+
**A file that does not exist yet keeps `mkstemp`'s 0600**, and that is
|
|
191
|
+
the whole of the new-file policy. The first attempt at this read the
|
|
192
|
+
process umask — `os.umask(0)` then put it back — to reproduce what
|
|
193
|
+
`open()` would have done. That is a widely-copied idiom and it is a
|
|
194
|
+
race: every file any other thread creates inside that window is born
|
|
195
|
+
world-writable. Trading a real race for a cosmetic default is a bad
|
|
196
|
+
bargain, and 0600 is the safer direction anyway. A user who wants the
|
|
197
|
+
file wider sets it once, and this function then preserves that.
|
|
198
|
+
"""
|
|
199
|
+
try:
|
|
200
|
+
return os.stat(target).st_mode & 0o777
|
|
201
|
+
except OSError:
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
|
|
181
205
|
def _stage_and_replace(target: Path, body: str) -> Path:
|
|
182
206
|
"""A fresh file, then an atomic rename over the name.
|
|
183
207
|
|
|
@@ -200,7 +224,28 @@ def _stage_and_replace(target: Path, body: str) -> Path:
|
|
|
200
224
|
)
|
|
201
225
|
staging = Path(staging_name)
|
|
202
226
|
try:
|
|
203
|
-
|
|
227
|
+
# mkstemp makes 0600; match the old mode. Through the *handle*,
|
|
228
|
+
# never `os.chmod(staging)`: a path-based chmod between mkstemp
|
|
229
|
+
# and the write is the time-of-check/time-of-use hole this whole
|
|
230
|
+
# module exists to close, so the portable-looking swap would buy
|
|
231
|
+
# Windows by weakening POSIX.
|
|
232
|
+
#
|
|
233
|
+
# **The mode is inherited, not asserted.** This hardcoded 0o644,
|
|
234
|
+
# which is fine for a new file and wrong for an existing one: a
|
|
235
|
+
# config someone had deliberately restricted to 0600 was widened
|
|
236
|
+
# to world-readable by the next write, silently, every time. The
|
|
237
|
+
# comment claimed to "match the old mode" while doing nothing of
|
|
238
|
+
# the sort. Found through SonarCloud's S2612 hotspot on the
|
|
239
|
+
# literal, which was pointing at a real defect rather than a
|
|
240
|
+
# style preference.
|
|
241
|
+
#
|
|
242
|
+
# `os.fchmod` is POSIX-only. Where it is absent the mode is left
|
|
243
|
+
# as mkstemp set it, which is *stricter* rather than looser, and
|
|
244
|
+
# POSIX mode bits are not what governs the file there anyway —
|
|
245
|
+
# the directory's inherited ACL is. D101.
|
|
246
|
+
mode = _mode_for(target)
|
|
247
|
+
if mode is not None and hasattr(os, "fchmod"):
|
|
248
|
+
os.fchmod(handle, mode)
|
|
204
249
|
written = 0
|
|
205
250
|
while written < len(encoded):
|
|
206
251
|
# A short write reported as success leaves a truncated
|
|
@@ -66,10 +66,11 @@ def local_tsc_analysis(root: Path) -> dict[str, Any] | None:
|
|
|
66
66
|
Runs across the repository root *and* any workspace beneath it that
|
|
67
67
|
carries a `tsconfig.json` — TS monorepos keep the config in `web/` or
|
|
68
68
|
`packages/*`, not the root, and a root-only check went blind on exactly
|
|
69
|
-
the repos this is for. Uses a `tsc`
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
the repos this is for. Uses a `tsc` **this machine** already provides,
|
|
70
|
+
found on PATH. Never installs, never runs `npx --yes`, never touches
|
|
71
|
+
the network — and never the audited tree's own
|
|
72
|
+
`node_modules/.bin/tsc`, which would let the repository choose the
|
|
73
|
+
binary this process runs (Decision 9; see `_tsc_on_path`).
|
|
73
74
|
|
|
74
75
|
An absent checker, or a `tsc` that cannot run usably, leaves coverage
|
|
75
76
|
*unknown*, not clean: a config error (FAILED, exit 3+) or a findings
|
|
@@ -81,13 +82,14 @@ def local_tsc_analysis(root: Path) -> dict[str, Any] | None:
|
|
|
81
82
|
"""
|
|
82
83
|
diagnostics: list[dict[str, Any]] = []
|
|
83
84
|
ran = False
|
|
85
|
+
if not _tsc_on_path():
|
|
86
|
+
return None
|
|
84
87
|
for project in _tsconfig_project_dirs(root):
|
|
85
|
-
tsc = _resolve_tsc(root, project)
|
|
86
|
-
if tsc is None:
|
|
87
|
-
continue
|
|
88
88
|
result = run(
|
|
89
89
|
"typescript",
|
|
90
|
-
|
|
90
|
+
# A constant, resolved by PATH. Never a path built from the
|
|
91
|
+
# audited tree — see `_tsc_on_path`.
|
|
92
|
+
Invocation(argv=("tsc", "--noEmit", "--pretty", "false"),
|
|
91
93
|
findings_exit_codes=(0, 1, 2)),
|
|
92
94
|
cwd=project,
|
|
93
95
|
)
|
|
@@ -125,14 +127,28 @@ def _tsconfig_project_dirs(root: Path) -> list[Path]:
|
|
|
125
127
|
return sorted(dirs)
|
|
126
128
|
|
|
127
129
|
|
|
128
|
-
def
|
|
129
|
-
"""
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
130
|
+
def _tsc_on_path() -> bool:
|
|
131
|
+
"""Whether a `tsc` **this machine** provides is available.
|
|
132
|
+
|
|
133
|
+
PATH only, and that is the whole point. This preferred the audited
|
|
134
|
+
tree's own `node_modules/.bin/tsc` and fell back to PATH, which let
|
|
135
|
+
the repository under audit choose the binary this process executes —
|
|
136
|
+
a `tsconfig.json` and a writable `node_modules/.bin/tsc` are all a
|
|
137
|
+
hostile tree needs, and it does not even have to contain TypeScript.
|
|
138
|
+
|
|
139
|
+
SECURITY.md says the sole path by which this agent executes code from
|
|
140
|
+
the tree is the operator's opt-in test command (Decision 9). That was
|
|
141
|
+
false while this function existed, so the resolution is deleted
|
|
142
|
+
rather than guarded: there is no safe way to run a binary whose
|
|
143
|
+
identity the audited repository controls.
|
|
144
|
+
|
|
145
|
+
The cost is disclosed and small. A project whose only compiler is
|
|
146
|
+
project-local now leaves type coverage **unknown** rather than clean,
|
|
147
|
+
which is the honest fallback the evidence model already has a word
|
|
148
|
+
for — and reading an absent checker as a clean one is the
|
|
149
|
+
absence-as-a-pass this module's own docstring forbids.
|
|
150
|
+
"""
|
|
151
|
+
return locate("tsc") is not None
|
|
136
152
|
|
|
137
153
|
|
|
138
154
|
def _parse_diagnostics(stdout: str, prefix: Path) -> list[dict[str, Any]]:
|
{maintainability_agent-2.7.0 → maintainability_agent-2.8.1}/src/maintainability_audit/baseline.py
RENAMED
|
@@ -7,6 +7,7 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
from ._finding_match import Identity, identities_from_report, rename_map, unmatched
|
|
9
9
|
from ._identity import finding_fingerprints
|
|
10
|
+
from .config import read_operator_file
|
|
10
11
|
|
|
11
12
|
__all__ = [
|
|
12
13
|
"finding_fingerprints",
|
|
@@ -38,7 +39,10 @@ def _read(path: str | None) -> dict[str, Any] | None:
|
|
|
38
39
|
baseline_path = Path(path)
|
|
39
40
|
if not baseline_path.exists():
|
|
40
41
|
return None
|
|
41
|
-
|
|
42
|
+
# Same validation the config door uses: `--baseline` is a path from
|
|
43
|
+
# the command line, and a FIFO or `/dev/zero` named here would block
|
|
44
|
+
# or exhaust memory rather than parse (SonarCloud S8707).
|
|
45
|
+
data = json.loads(read_operator_file(baseline_path))
|
|
42
46
|
version = data.get("version", 1)
|
|
43
47
|
if version != BASELINE_VERSION:
|
|
44
48
|
# Failing closed matters more than convenience here. Loading an old
|