maintainability-agent 2.4.1__tar.gz → 2.5.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.
Files changed (317) hide show
  1. {maintainability_agent-2.4.1/src/maintainability_agent.egg-info → maintainability_agent-2.5.0}/PKG-INFO +2 -2
  2. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/README.md +1 -1
  3. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/pyproject.toml +1 -1
  4. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0/src/maintainability_agent.egg-info}/PKG-INFO +2 -2
  5. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_agent.egg-info/SOURCES.txt +2 -0
  6. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/__init__.py +1 -1
  7. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_first_run.py +54 -2
  8. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_mcp_setup.py +18 -4
  9. maintainability_agent-2.5.0/src/maintainability_audit/_test_commands.py +251 -0
  10. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/config.py +1 -1
  11. maintainability_agent-2.5.0/tests/test_test_commands.py +348 -0
  12. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/LICENSE +0 -0
  13. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/setup.cfg +0 -0
  14. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_agent.egg-info/dependency_links.txt +0 -0
  15. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_agent.egg-info/entry_points.txt +0 -0
  16. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_agent.egg-info/requires.txt +0 -0
  17. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_agent.egg-info/top_level.txt +0 -0
  18. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/__main__.py +0 -0
  19. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_adapters.py +0 -0
  20. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_analysis.py +0 -0
  21. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_aspects.py +0 -0
  22. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_assets/analyzer-catalog.json +0 -0
  23. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_assets/standard.md +0 -0
  24. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_attestation.py +0 -0
  25. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_backfill.py +0 -0
  26. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_bands.py +0 -0
  27. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_built_ins.py +0 -0
  28. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_calibration.py +0 -0
  29. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_catalog.py +0 -0
  30. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_charts.py +0 -0
  31. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_cognitive.py +0 -0
  32. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_config_defaults.py +0 -0
  33. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_conformance.py +0 -0
  34. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_corroborate.py +0 -0
  35. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_coverage_notes.py +0 -0
  36. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_derive.py +0 -0
  37. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_discovery.py +0 -0
  38. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_documents.py +0 -0
  39. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_economics.py +0 -0
  40. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_economics_view.py +0 -0
  41. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_environment.py +0 -0
  42. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_evidence_view.py +0 -0
  43. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_finding_match.py +0 -0
  44. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_formula.py +0 -0
  45. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_gates.py +0 -0
  46. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_generic.py +0 -0
  47. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_grant_ledger.py +0 -0
  48. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_history_view.py +0 -0
  49. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_hostile_prompt.py +0 -0
  50. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_hotspots.py +0 -0
  51. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_html_report_sections.py +0 -0
  52. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_html_view.py +0 -0
  53. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_identity.py +0 -0
  54. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_jvm_adapters.py +0 -0
  55. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_markdown_sections.py +0 -0
  56. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_masking.py +0 -0
  57. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_mcp_audit.py +0 -0
  58. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_mcp_gate.py +0 -0
  59. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_mcp_grants.py +0 -0
  60. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_mcp_refusals.py +0 -0
  61. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_mcp_resources.py +0 -0
  62. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_metric_adapters.py +0 -0
  63. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_metrics_types.py +0 -0
  64. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_pillars.py +0 -0
  65. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_practice.py +0 -0
  66. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_pressures.py +0 -0
  67. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_ranges_c.py +0 -0
  68. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_ranges_core.py +0 -0
  69. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_ranges_cpp.py +0 -0
  70. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_ranges_csharp.py +0 -0
  71. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_ranges_fortran.py +0 -0
  72. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_ranges_java.py +0 -0
  73. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_ranges_js.py +0 -0
  74. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_ranges_swift.py +0 -0
  75. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_ratchet.py +0 -0
  76. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_recurrence.py +0 -0
  77. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_runner.py +0 -0
  78. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_safe_write.py +0 -0
  79. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_scan_history.py +0 -0
  80. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_scan_view.py +0 -0
  81. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_second_source.py +0 -0
  82. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_selection.py +0 -0
  83. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_semantic.py +0 -0
  84. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_semantic_policy.py +0 -0
  85. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_semantic_ts.py +0 -0
  86. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_semantic_view.py +0 -0
  87. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_setup_errors.py +0 -0
  88. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_setup_persist.py +0 -0
  89. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_skill_data/SKILL.md +0 -0
  90. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_skill_data/agents/anthropic.yaml +0 -0
  91. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_skill_data/agents/copilot.yaml +0 -0
  92. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_skill_data/agents/openai.yaml +0 -0
  93. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_skill_data/copilot/maintainability-agent.prompt.md +0 -0
  94. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_skill_data/references/finding-taxonomy.md +0 -0
  95. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_skill_install.py +0 -0
  96. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_stored_grants.py +0 -0
  97. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_tdd_view.py +0 -0
  98. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_test_execution.py +0 -0
  99. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_test_pairing.py +0 -0
  100. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_tokens.py +0 -0
  101. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_tool_adapters.py +0 -0
  102. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_trends.py +0 -0
  103. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_user_config.py +0 -0
  104. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_verdict_adapters.py +0 -0
  105. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_verification.py +0 -0
  106. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_work_order.py +0 -0
  107. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_work_order_view.py +0 -0
  108. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_work_order_weights.py +0 -0
  109. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/_xml.py +0 -0
  110. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/baseline.py +0 -0
  111. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/cli.py +0 -0
  112. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/deadcode.py +0 -0
  113. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/declarations.py +0 -0
  114. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/duplication.py +0 -0
  115. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/evidence.py +0 -0
  116. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/git_tools.py +0 -0
  117. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/history.py +0 -0
  118. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/idioms.py +0 -0
  119. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/instructions.py +0 -0
  120. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/mcp_server.py +0 -0
  121. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/metrics.py +0 -0
  122. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/prompts.py +0 -0
  123. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/renderers.py +0 -0
  124. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/report.py +0 -0
  125. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/sarif.py +0 -0
  126. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/scoring.py +0 -0
  127. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/similarity.py +0 -0
  128. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/src/maintainability_audit/source.py +0 -0
  129. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_acquisition_trust.py +0 -0
  130. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_adapter_docs.py +0 -0
  131. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_adapter_recordings.py +0 -0
  132. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_adapters.py +0 -0
  133. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_added_lines.py +0 -0
  134. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_analysis_coverage.py +0 -0
  135. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_analysis_execution.py +0 -0
  136. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_analyzer_bridge.py +0 -0
  137. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_analyzer_config_isolation.py +0 -0
  138. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_analyzer_estimate_claims.py +0 -0
  139. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_analyzer_pinning.py +0 -0
  140. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_analyzer_primary_estimate.py +0 -0
  141. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_analyzer_provenance.py +0 -0
  142. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_analyzer_provenance_exclusions.py +0 -0
  143. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_analyzer_scoring_path.py +0 -0
  144. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_analyzer_spread_range.py +0 -0
  145. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_analyzer_xml_bounds.py +0 -0
  146. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_annotations_match_behaviour.py +0 -0
  147. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_anticipated_refusals.py +0 -0
  148. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_architecture.py +0 -0
  149. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_artifact_write_route_class.py +0 -0
  150. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_aspect_coverage.py +0 -0
  151. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_asset_snapshot_class.py +0 -0
  152. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_ast_reading_resolves_spellings.py +0 -0
  153. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_attestation.py +0 -0
  154. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_audit_components.py +0 -0
  155. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_authorization_freshness.py +0 -0
  156. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_authorship_gates.py +0 -0
  157. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_backfill.py +0 -0
  158. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_band_concealment_class.py +0 -0
  159. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_band_concealment_p3.py +0 -0
  160. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_band_pressures.py +0 -0
  161. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_bands.py +0 -0
  162. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_body_or_signature_class.py +0 -0
  163. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_c_declarations.py +0 -0
  164. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_calibration_corpus.py +0 -0
  165. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_calibration_drift.py +0 -0
  166. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_calibration_tooling.py +0 -0
  167. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_catalog_selection.py +0 -0
  168. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_catalog_tier_promise.py +0 -0
  169. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_chat_primary_docs.py +0 -0
  170. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_checkstyle_adapter.py +0 -0
  171. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_checkstyle_catalog.py +0 -0
  172. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_ci_history_recipe.py +0 -0
  173. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_ci_installs_the_analyzer_pool.py +0 -0
  174. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_claimed_languages.py +0 -0
  175. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_cli.py +0 -0
  176. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_clone_group_class.py +0 -0
  177. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_cognitive_complexity.py +0 -0
  178. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_cohort_statistics.py +0 -0
  179. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_concealment_properties.py +0 -0
  180. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_config_shape.py +0 -0
  181. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_consent_and_grant.py +0 -0
  182. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_consumer_migration.py +0 -0
  183. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_contract_claims.py +0 -0
  184. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_corroborate.py +0 -0
  185. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_coverage_by_language.py +0 -0
  186. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_cpp_declarations.py +0 -0
  187. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_csharp_declarations.py +0 -0
  188. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_d15_composition.py +0 -0
  189. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_d15_goal_directed.py +0 -0
  190. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_dead_code.py +0 -0
  191. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_declaration_edge_cases.py +0 -0
  192. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_declaration_grading.py +0 -0
  193. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_declaration_ranges.py +0 -0
  194. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_declaration_source.py +0 -0
  195. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_declared_authorship.py +0 -0
  196. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_declared_imports.py +0 -0
  197. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_derivation_mix.py +0 -0
  198. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_determinism.py +0 -0
  199. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_dimension_ratchet.py +0 -0
  200. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_direct_runner_availability_class.py +0 -0
  201. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_discovery.py +0 -0
  202. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_doc_claims.py +0 -0
  203. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_docs_links.py +0 -0
  204. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_economic_context.py +0 -0
  205. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_empty_output_not_clean.py +0 -0
  206. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_empty_output_not_clean_adapters.py +0 -0
  207. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_environment_work_order.py +0 -0
  208. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_evidence_invariants.py +0 -0
  209. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_evidence_normalization.py +0 -0
  210. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_evidence_properties.py +0 -0
  211. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_exclusion_dialects.py +0 -0
  212. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_falsifier_standard.py +0 -0
  213. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_file_list_cap_class.py +0 -0
  214. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_finding_identity.py +0 -0
  215. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_first_run_elicitation.py +0 -0
  216. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_first_run_prompt.py +0 -0
  217. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_first_run_reelicitation.py +0 -0
  218. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_fix_breadth_window.py +0 -0
  219. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_fixed_form_fortran.py +0 -0
  220. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_format_ask.py +0 -0
  221. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_fortran_declarations.py +0 -0
  222. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_fortran_metrics.py +0 -0
  223. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_fortran_practice.py +0 -0
  224. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_git_argv.py +0 -0
  225. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_git_read_only.py +0 -0
  226. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_git_readonly_class.py +0 -0
  227. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_grant_only_user_tier.py +0 -0
  228. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_history.py +0 -0
  229. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_history_boundary.py +0 -0
  230. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_history_manifest.py +0 -0
  231. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_history_schema2.py +0 -0
  232. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_history_window.py +0 -0
  233. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_hostile_prompt.py +0 -0
  234. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_html_executive.py +0 -0
  235. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_html_section_parity_class.py +0 -0
  236. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_identity_docs.py +0 -0
  237. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_identity_resolution.py +0 -0
  238. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_idiom_divergence.py +0 -0
  239. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_installed_wheel_runtime.py +0 -0
  240. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_instruction_targets.py +0 -0
  241. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_java_range_guard.py +0 -0
  242. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_java_ranges.py +0 -0
  243. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_java_scored.py +0 -0
  244. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_java_wiring.py +0 -0
  245. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_js_complexity_operators.py +0 -0
  246. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_js_declarations.py +0 -0
  247. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_language_coverage.py +0 -0
  248. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_mcp_baseline_payload.py +0 -0
  249. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_mcp_history.py +0 -0
  250. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_mcp_server.py +0 -0
  251. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_migration_1_0.py +0 -0
  252. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_near_duplicates.py +0 -0
  253. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_network_disclosure.py +0 -0
  254. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_one_setup_across_surfaces_class.py +0 -0
  255. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_pattern_budget.py +0 -0
  256. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_phase6_claims.py +0 -0
  257. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_pillars_and_practice.py +0 -0
  258. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_platform_claim.py +0 -0
  259. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_pmd_adapter.py +0 -0
  260. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_pmd_audit_closures.py +0 -0
  261. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_pool_runs_by_config.py +0 -0
  262. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_population_floors.py +0 -0
  263. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_practice_nested_gates_class.py +0 -0
  264. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_presentation_refusals.py +0 -0
  265. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_promises.py +0 -0
  266. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_prompt_escalation_once.py +0 -0
  267. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_prompt_honesty.py +0 -0
  268. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_prompt_location_escalation_class.py +0 -0
  269. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_prove_falsifiers.py +0 -0
  270. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_python_declarations.py +0 -0
  271. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_read_boundary.py +0 -0
  272. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_readme_claims.py +0 -0
  273. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_recurrence.py +0 -0
  274. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_release_plan.py +0 -0
  275. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_release_plan_status.py +0 -0
  276. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_report_remedy_copy.py +0 -0
  277. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_roles_recorded.py +0 -0
  278. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_root_grants.py +0 -0
  279. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_rubric_ignores_project_lint_config.py +0 -0
  280. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_runner.py +0 -0
  281. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_sarif.py +0 -0
  282. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_scan_history.py +0 -0
  283. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_scan_scope.py +0 -0
  284. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_scanning.py +0 -0
  285. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_schema_fidelity.py +0 -0
  286. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_scope_conformance.py +0 -0
  287. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_scoring_calibration.py +0 -0
  288. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_semantic_policy.py +0 -0
  289. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_setup_gate_completeness.py +0 -0
  290. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_setup_gate_independent_contract.py +0 -0
  291. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_setup_precondition.py +0 -0
  292. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_skill_install.py +0 -0
  293. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_source_index.py +0 -0
  294. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_spotbugs_adapter.py +0 -0
  295. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_spotbugs_catalog.py +0 -0
  296. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_stage8_contract.py +0 -0
  297. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_suite_isolation.py +0 -0
  298. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_swift_declarations.py +0 -0
  299. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_tdd_structure.py +0 -0
  300. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_test_execution_class.py +0 -0
  301. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_three_presentations.py +0 -0
  302. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_trends.py +0 -0
  303. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_type_fields_not_functions_class.py +0 -0
  304. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_uat_falsifiers.py +0 -0
  305. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_unread_code.py +0 -0
  306. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_user_config_tier.py +0 -0
  307. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_verification_audit_closures.py +0 -0
  308. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_verified_grade.py +0 -0
  309. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_version_claim.py +0 -0
  310. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_wheel_contents.py +0 -0
  311. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_work_order.py +0 -0
  312. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_work_order_items.py +0 -0
  313. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_work_order_selection.py +0 -0
  314. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_workflow_supply_chain.py +0 -0
  315. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_write_boundary.py +0 -0
  316. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_write_route_class.py +0 -0
  317. {maintainability_agent-2.4.1 → maintainability_agent-2.5.0}/tests/test_written_record.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maintainability-agent
3
- Version: 2.4.1
3
+ Version: 2.5.0
4
4
  Summary: Maintainability gate and AI remediation prompt generator for CI.
5
5
  Author: Marshall Guillory
6
6
  License: MIT
@@ -44,7 +44,7 @@ Dynamic: license-file
44
44
  **A deterministic, offline maintainability audit whose output is a _bounded
45
45
  work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
46
46
  says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
47
- Version **2.4.1**.
47
+ Version **2.5.0**.
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
@@ -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.4.1**.
8
+ Version **2.5.0**.
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "maintainability-agent"
7
- version = "2.4.1"
7
+ version = "2.5.0"
8
8
  description = "Maintainability gate and AI remediation prompt generator for CI."
9
9
  readme = "README.md"
10
10
  # 3.11, not 3.10. `_discovery`, `_pillars` and `_runner` all import
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maintainability-agent
3
- Version: 2.4.1
3
+ Version: 2.5.0
4
4
  Summary: Maintainability gate and AI remediation prompt generator for CI.
5
5
  Author: Marshall Guillory
6
6
  License: MIT
@@ -44,7 +44,7 @@ Dynamic: license-file
44
44
  **A deterministic, offline maintainability audit whose output is a _bounded
45
45
  work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
46
46
  says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
47
- Version **2.4.1**.
47
+ Version **2.5.0**.
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
@@ -82,6 +82,7 @@ src/maintainability_audit/_setup_persist.py
82
82
  src/maintainability_audit/_skill_install.py
83
83
  src/maintainability_audit/_stored_grants.py
84
84
  src/maintainability_audit/_tdd_view.py
85
+ src/maintainability_audit/_test_commands.py
85
86
  src/maintainability_audit/_test_execution.py
86
87
  src/maintainability_audit/_test_pairing.py
87
88
  src/maintainability_audit/_tokens.py
@@ -293,6 +294,7 @@ tests/test_stage8_contract.py
293
294
  tests/test_suite_isolation.py
294
295
  tests/test_swift_declarations.py
295
296
  tests/test_tdd_structure.py
297
+ tests/test_test_commands.py
296
298
  tests/test_test_execution_class.py
297
299
  tests/test_three_presentations.py
298
300
  tests/test_trends.py
@@ -1,3 +1,3 @@
1
1
  """Maintainability Audit CI."""
2
2
 
3
- __version__ = "2.4.1"
3
+ __version__ = "2.5.0"
@@ -22,6 +22,7 @@ sat in ``DEFAULTS``, documented as reserved, read by nothing, from
22
22
  """
23
23
  from __future__ import annotations
24
24
 
25
+ import contextlib
25
26
  import json
26
27
  import sys
27
28
  from pathlib import Path
@@ -190,11 +191,53 @@ def maybe_prompt_economics(root: Path, config: dict) -> None:
190
191
  print(f"Wrote {written}")
191
192
 
192
193
 
194
+ def _input_with_default(prompt: str, default: str) -> str:
195
+ """`input()` with the line pre-filled, where the terminal allows it.
196
+
197
+ `readline` is what makes the default *editable* rather than merely
198
+ described: the text is inserted into the line buffer, so Enter submits
199
+ it, backspace edits it, and clearing it returns the empty string that
200
+ has always meant "cancel". Without that, a pre-filled default and a
201
+ blank-means-cancel rule cannot both be true on one keystroke.
202
+
203
+ Absent or unusable `readline` — Windows without pyreadline, a terminal
204
+ that refuses the hook — falls back to a plain `input()`. The default is
205
+ still named in the prompt text, so the operator loses the keystroke and
206
+ not the information. Never let this be the thing that fails a setup.
207
+ """
208
+ if not default:
209
+ return input(prompt)
210
+ try:
211
+ import readline
212
+ except ImportError:
213
+ return input(prompt)
214
+
215
+ def _prefill() -> None:
216
+ readline.insert_text(default)
217
+ readline.redisplay()
218
+
219
+ try:
220
+ readline.set_startup_hook(_prefill)
221
+ return input(prompt)
222
+ except Exception: # noqa: BLE001 - a cosmetic hook must not fail setup
223
+ return input(prompt)
224
+ finally:
225
+ # Global state: a hook left installed would pre-type this command
226
+ # into whatever the session asks next.
227
+ with contextlib.suppress(Exception):
228
+ readline.set_startup_hook()
229
+
230
+
193
231
  def maybe_prompt_test_command(root: Path, config: dict) -> None:
194
232
  """Ask the opted-in operator for the test command, once -- the CLI half
195
233
  of the Class 5 second stage, mirroring the labor-rate ask. Fires only at
196
234
  a TTY, only when the suite was opted into and no command is stored, and
197
235
  a blank answer cancels the opt-in. Nothing runs until a command exists.
236
+
237
+ Where the tree's manifests name a runner, the line is **pre-filled**
238
+ with that command rather than merely mentioning it, so this surface and
239
+ the MCP one carry the same default in the same field: Enter accepts,
240
+ editing replaces, and clearing the line cancels exactly as before.
198
241
  """
199
242
  import shlex
200
243
 
@@ -209,10 +252,19 @@ def maybe_prompt_test_command(root: Path, config: dict) -> None:
209
252
  if (config.get("expected_commands") or {}).get("test"):
210
253
  return
211
254
 
212
- answer = input(
255
+ from ._test_commands import suggested_test_command
256
+
257
+ suggested = suggested_test_command(root)
258
+ detected = " ".join(suggested.command) if suggested is not None else ""
259
+ prompt = (
260
+ f"Detected `{detected}` from {suggested.evidence}. The command that "
261
+ "runs this repository's test suite (Enter to accept, clear the line "
262
+ "to cancel running it): "
263
+ if suggested is not None else
213
264
  "The command that runs this repository's test suite, e.g. `pytest` "
214
265
  "(Enter to cancel running it): "
215
- ).strip()
266
+ )
267
+ answer = _input_with_default(prompt, detected).strip()
216
268
 
217
269
  from ._safe_write import write_bounded
218
270
 
@@ -226,21 +226,35 @@ def run_tests_pending(root: Path) -> bool:
226
226
  and "test_execution" not in stored)
227
227
 
228
228
 
229
- def test_command_questions() -> list[dict[str, Any]]:
229
+ def test_command_questions(root: Path | None = None) -> list[dict[str, Any]]:
230
230
  """The one command, asked only of someone who opted into running it.
231
231
 
232
232
  A free-text answer (empty options), the second stage of the opt-in the
233
233
  way the labor rates are the second stage of the economic scenario.
234
+
235
+ When the tree's manifests name a runner, that command is the question's
236
+ `default` and the prompt says which file proposed it. It is a default,
237
+ not a decision: the operator's answer is what gets stored, and blank
238
+ still cancels the opt-in — a pre-filled field the operator clears is
239
+ the same cancellation it always was.
234
240
  """
241
+ from ._test_commands import suggested_test_command
242
+
243
+ suggested = suggested_test_command(root) if root is not None else None
244
+ detected = " ".join(suggested.command) if suggested is not None else ""
245
+ because = (
246
+ f"Detected `{detected}` from {suggested.evidence}. "
247
+ if suggested is not None else ""
248
+ )
235
249
  return [{
236
250
  "name": "test_command",
237
251
  "prompt": (
238
- "The command that runs this repository's test suite "
252
+ f"{because}The command that runs this repository's test suite "
239
253
  "(e.g. `pytest`, `npm test`). It runs in the repository root, "
240
254
  "and leaving it blank cancels the opt-in."
241
255
  ),
242
256
  "options": [],
243
- "default": "",
257
+ "default": detected,
244
258
  }]
245
259
 
246
260
 
@@ -334,7 +348,7 @@ def setup_schema(root: Path | None = None):
334
348
  if root is not None and economics_bounds_pending(root):
335
349
  return _schema_for(economics_bound_questions())
336
350
  if root is not None and test_command_pending(root):
337
- return _schema_for(test_command_questions())
351
+ return _schema_for(test_command_questions(root))
338
352
  return _schema_for(setup_questions(load_config(None)))
339
353
 
340
354
 
@@ -0,0 +1,251 @@
1
+ """The command that runs a repository's tests, read from its build files.
2
+
3
+ `expected_commands.test` has always been hand-typed. The prompt's example
4
+ was `pytest`, so on every non-Python tree the operator was asked a question
5
+ the repository could already answer: a `Package.swift` beside a `Tests/`
6
+ directory says `swift test` as plainly as a manifest says anything.
7
+
8
+ **This suggests; it never decides.** The `require_test_command` hard gate
9
+ reads "a *documented* test command", and a command this module wrote into
10
+ the config unasked would satisfy that gate while nobody had documented
11
+ anything — a gate passing on evidence it invented. So detection reaches
12
+ exactly one place: the default offered in the setup question, on every
13
+ surface. A human still answers it, and their answer is what gets stored.
14
+
15
+ **Every rule is anchored to a manifest**, never to a directory name alone
16
+ or a file extension. `go.mod` means the tree is a Go module; a stray
17
+ `.go` file means someone vendored something. The manifest is recorded as
18
+ `evidence` and travels with the suggestion, so the question can say *why*
19
+ it is proposing this and the operator can disagree with a reason.
20
+
21
+ **What it refuses to guess**, because a command that cannot run is worse
22
+ than no suggestion:
23
+
24
+ - **An Xcode project with no `Package.swift`.** `xcodebuild test` needs
25
+ `-scheme`, and usually `-destination` as well; neither is derivable from
26
+ the tree with any confidence. The roadmap named `xcodebuild test` as a
27
+ target and it is deliberately not shipped: bare `xcodebuild test` fails
28
+ on every project it would be offered for, and a suggestion that always
29
+ fails teaches the operator to ignore suggestions.
30
+ - **`npm test` where `scripts.test` is the placeholder** npm generates
31
+ (`echo "Error: no test specified" && exit 1`). It is a script that
32
+ exists in order not to be a test command.
33
+ - **A package with no tests.** `swift test` on a package with no test
34
+ target is an error, so `Tests/` or a `.testTarget` in the manifest is
35
+ required, not just `Package.swift`.
36
+
37
+ Nothing here runs anything. Detection is file reading, and whether the
38
+ suite may be *executed* remains the Class 5 opt-in, default off.
39
+ """
40
+ from __future__ import annotations
41
+
42
+ import json
43
+ import re
44
+ from dataclasses import dataclass
45
+ from pathlib import Path
46
+
47
+ #: Bounded like `_practice._read`: a manifest is small, and a multi-megabyte
48
+ #: `package.json` is not a reason to read a multi-megabyte file.
49
+ _READ_LIMIT = 200_000
50
+
51
+ #: The npm placeholder, written by `npm init` so that `scripts.test` exists.
52
+ #: Matching it loosely on purpose — the wording has changed across npm
53
+ #: versions and the shape has not.
54
+ _NPM_PLACEHOLDER = re.compile(r"no test specified", re.IGNORECASE)
55
+
56
+ _CMAKE_TESTS = re.compile(r"^\s*(?:enable_testing\s*\(|add_test\s*\()", re.MULTILINE)
57
+ _SWIFT_TEST_TARGET = re.compile(r"\.testTarget\s*\(")
58
+ _PYTEST_DECLARED = re.compile(
59
+ r"^\s*\[(?:tool\.pytest[.\]]|pytest\]|tool:pytest\])", re.MULTILINE
60
+ )
61
+
62
+
63
+ @dataclass(frozen=True)
64
+ class TestCommand:
65
+ """A suggested command and the file that suggested it."""
66
+
67
+ command: list[str]
68
+ evidence: str
69
+
70
+
71
+ def _read(path: Path) -> str:
72
+ try:
73
+ with path.open("r", encoding="utf-8", errors="replace") as handle:
74
+ return handle.read(_READ_LIMIT)
75
+ except OSError:
76
+ return ""
77
+
78
+
79
+ def _swift(root: Path) -> TestCommand | None:
80
+ """SwiftPM, and only where a test target exists to run.
81
+
82
+ `swift test` on a package without one exits non-zero, so `Package.swift`
83
+ alone is not enough. Either spelling of "there are tests here" counts:
84
+ the conventional `Tests/` directory, or a `.testTarget` the manifest
85
+ declares somewhere else.
86
+ """
87
+ manifest = root / "Package.swift"
88
+ if not manifest.is_file():
89
+ return None
90
+ has_tests = (root / "Tests").is_dir() or bool(
91
+ _SWIFT_TEST_TARGET.search(_read(manifest))
92
+ )
93
+ return TestCommand(["swift", "test"], "Package.swift") if has_tests else None
94
+
95
+
96
+ def _fortran(root: Path) -> TestCommand | None:
97
+ """`fpm.toml` is the Fortran Package Manager's manifest.
98
+
99
+ `_practice` already reads this file for fortitude's lint settings, so
100
+ the tree shape is one this tool understood before it could name the
101
+ command that tests it.
102
+ """
103
+ return (
104
+ TestCommand(["fpm", "test"], "fpm.toml")
105
+ if (root / "fpm.toml").is_file()
106
+ else None
107
+ )
108
+
109
+
110
+ def _rust(root: Path) -> TestCommand | None:
111
+ return (
112
+ TestCommand(["cargo", "test"], "Cargo.toml")
113
+ if (root / "Cargo.toml").is_file()
114
+ else None
115
+ )
116
+
117
+
118
+ def _go(root: Path) -> TestCommand | None:
119
+ """`./...` because a bare `go test` tests only the root package.
120
+
121
+ The difference matters in a module with any structure at all: the bare
122
+ form silently tests almost nothing and exits zero, which is the worst
123
+ available outcome for a command whose job is to fail.
124
+ """
125
+ return (
126
+ TestCommand(["go", "test", "./..."], "go.mod")
127
+ if (root / "go.mod").is_file()
128
+ else None
129
+ )
130
+
131
+
132
+ def _cmake(root: Path) -> TestCommand | None:
133
+ """`ctest`, but only where the project actually registered tests.
134
+
135
+ A `CMakeLists.txt` without `enable_testing()` or `add_test()` builds
136
+ something that has no tests to run, and `ctest` there reports "No tests
137
+ were found" and exits non-zero.
138
+ """
139
+ manifest = root / "CMakeLists.txt"
140
+ if not manifest.is_file() or not _CMAKE_TESTS.search(_read(manifest)):
141
+ return None
142
+ return TestCommand(["ctest"], "CMakeLists.txt")
143
+
144
+
145
+ def _node(root: Path) -> TestCommand | None:
146
+ """`npm test`, unless the script is npm's own placeholder."""
147
+ manifest = root / "package.json"
148
+ if not manifest.is_file():
149
+ return None
150
+ try:
151
+ parsed = json.loads(_read(manifest))
152
+ except (json.JSONDecodeError, ValueError):
153
+ return None
154
+ if not isinstance(parsed, dict):
155
+ return None
156
+ script = (parsed.get("scripts") or {}).get("test")
157
+ if not isinstance(script, str) or not script.strip():
158
+ return None
159
+ if _NPM_PLACEHOLDER.search(script):
160
+ return None
161
+ return TestCommand(["npm", "test"], "package.json")
162
+
163
+
164
+ def _python(root: Path) -> TestCommand | None:
165
+ """pytest where a file declares it, not where a `tests/` folder exists.
166
+
167
+ A directory named `tests` says nothing about the runner. A
168
+ `[tool.pytest.ini_options]`, `[pytest]` or `[tool:pytest]` section is a
169
+ repository stating which runner it uses, which is the only thing worth
170
+ reading it for.
171
+ """
172
+ if (root / "pytest.ini").is_file():
173
+ return TestCommand(["pytest"], "pytest.ini")
174
+ for name in ("pyproject.toml", "tox.ini", "setup.cfg"):
175
+ manifest = root / name
176
+ if manifest.is_file() and _PYTEST_DECLARED.search(_read(manifest)):
177
+ return TestCommand(["pytest"], name)
178
+ return None
179
+
180
+
181
+ def _maven(root: Path) -> TestCommand | None:
182
+ return (
183
+ TestCommand(["mvn", "test"], "pom.xml")
184
+ if (root / "pom.xml").is_file()
185
+ else None
186
+ )
187
+
188
+
189
+ def _gradle(root: Path) -> TestCommand | None:
190
+ """The wrapper when the repository ships one, because that is the point.
191
+
192
+ A checked-in `gradlew` pins the Gradle version the build expects;
193
+ calling bare `gradle` uses whatever the machine happens to have, which
194
+ is the thing the wrapper exists to prevent.
195
+ """
196
+ for name in ("build.gradle", "build.gradle.kts"):
197
+ if (root / name).is_file():
198
+ wrapper = (root / "gradlew").is_file()
199
+ return TestCommand(["./gradlew" if wrapper else "gradle", "test"], name)
200
+ return None
201
+
202
+
203
+ def _dotnet(root: Path) -> TestCommand | None:
204
+ for pattern in ("*.sln", "*.slnx"):
205
+ found = sorted(root.glob(pattern))
206
+ if found:
207
+ return TestCommand(["dotnet", "test"], found[0].name)
208
+ projects = sorted(root.glob("*.csproj"))
209
+ return TestCommand(["dotnet", "test"], projects[0].name) if projects else None
210
+
211
+
212
+ #: Order is the tie-break and is therefore part of the contract: a tree with
213
+ #: both a `Package.swift` and a `package.json` gets one answer, the same one
214
+ #: every time (P1). Ecosystems whose manifest is unambiguous come first;
215
+ #: `package.json` is late because it is the file most often present in a
216
+ #: repository whose real subject is something else.
217
+ _DETECTORS = (
218
+ _swift,
219
+ _fortran,
220
+ _rust,
221
+ _go,
222
+ _cmake,
223
+ _maven,
224
+ _gradle,
225
+ _dotnet,
226
+ _python,
227
+ _node,
228
+ )
229
+
230
+
231
+ def detect_test_commands(root: Path) -> list[TestCommand]:
232
+ """Every command this tree's manifests support, in a fixed order.
233
+
234
+ Plural because a repository may honestly have more than one — a Swift
235
+ package with a Node-based docs site is not a malformed tree — and
236
+ reporting only the first would hide that from anyone deciding.
237
+ """
238
+ root = Path(root)
239
+ found = [detector(root) for detector in _DETECTORS]
240
+ return [item for item in found if item is not None]
241
+
242
+
243
+ def suggested_test_command(root: Path) -> TestCommand | None:
244
+ """The one command to offer as the setup question's default, or `None`.
245
+
246
+ `None` is a real answer and the common one: most trees this tool audits
247
+ will not carry a manifest it recognises, and the question is then asked
248
+ exactly as it always was.
249
+ """
250
+ found = detect_test_commands(root)
251
+ return found[0] if found else None
@@ -11,7 +11,7 @@ from ._config_defaults import DEFAULT_CONFIG, DEFAULT_IDIOM_GROUPS
11
11
  # still the door every caller comes through.
12
12
  __all__ = ["DEFAULT_CONFIG", "DEFAULT_IDIOM_GROUPS", "PROJECT_URL", "VERSION"]
13
13
 
14
- VERSION = "2.4.1"
14
+ VERSION = "2.5.0"
15
15
 
16
16
  PROJECT_URL = "https://github.com/marshallguillory86/maintainability-agent"
17
17