maintainability-agent 2.3.0__tar.gz → 2.4.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.
Files changed (315) hide show
  1. {maintainability_agent-2.3.0/src/maintainability_agent.egg-info → maintainability_agent-2.4.1}/PKG-INFO +49 -10
  2. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/README.md +48 -9
  3. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/pyproject.toml +1 -1
  4. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1/src/maintainability_agent.egg-info}/PKG-INFO +49 -10
  5. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_agent.egg-info/SOURCES.txt +3 -0
  6. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/__init__.py +1 -1
  7. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_cognitive.py +21 -4
  8. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_config_defaults.py +1 -0
  9. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_metrics_types.py +14 -0
  10. maintainability_agent-2.4.1/src/maintainability_audit/_ranges_swift.py +215 -0
  11. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/config.py +1 -1
  12. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/declarations.py +15 -2
  13. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_claimed_languages.py +18 -0
  14. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_mcp_server.py +8 -6
  15. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_population_floors.py +30 -0
  16. maintainability_agent-2.4.1/tests/test_suite_isolation.py +139 -0
  17. maintainability_agent-2.4.1/tests/test_swift_declarations.py +264 -0
  18. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/LICENSE +0 -0
  19. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/setup.cfg +0 -0
  20. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_agent.egg-info/dependency_links.txt +0 -0
  21. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_agent.egg-info/entry_points.txt +0 -0
  22. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_agent.egg-info/requires.txt +0 -0
  23. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_agent.egg-info/top_level.txt +0 -0
  24. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/__main__.py +0 -0
  25. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_adapters.py +0 -0
  26. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_analysis.py +0 -0
  27. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_aspects.py +0 -0
  28. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_assets/analyzer-catalog.json +0 -0
  29. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_assets/standard.md +0 -0
  30. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_attestation.py +0 -0
  31. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_backfill.py +0 -0
  32. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_bands.py +0 -0
  33. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_built_ins.py +0 -0
  34. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_calibration.py +0 -0
  35. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_catalog.py +0 -0
  36. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_charts.py +0 -0
  37. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_conformance.py +0 -0
  38. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_corroborate.py +0 -0
  39. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_coverage_notes.py +0 -0
  40. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_derive.py +0 -0
  41. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_discovery.py +0 -0
  42. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_documents.py +0 -0
  43. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_economics.py +0 -0
  44. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_economics_view.py +0 -0
  45. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_environment.py +0 -0
  46. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_evidence_view.py +0 -0
  47. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_finding_match.py +0 -0
  48. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_first_run.py +0 -0
  49. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_formula.py +0 -0
  50. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_gates.py +0 -0
  51. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_generic.py +0 -0
  52. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_grant_ledger.py +0 -0
  53. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_history_view.py +0 -0
  54. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_hostile_prompt.py +0 -0
  55. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_hotspots.py +0 -0
  56. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_html_report_sections.py +0 -0
  57. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_html_view.py +0 -0
  58. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_identity.py +0 -0
  59. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_jvm_adapters.py +0 -0
  60. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_markdown_sections.py +0 -0
  61. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_masking.py +0 -0
  62. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_mcp_audit.py +0 -0
  63. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_mcp_gate.py +0 -0
  64. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_mcp_grants.py +0 -0
  65. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_mcp_refusals.py +0 -0
  66. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_mcp_resources.py +0 -0
  67. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_mcp_setup.py +0 -0
  68. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_metric_adapters.py +0 -0
  69. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_pillars.py +0 -0
  70. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_practice.py +0 -0
  71. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_pressures.py +0 -0
  72. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_ranges_c.py +0 -0
  73. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_ranges_core.py +0 -0
  74. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_ranges_cpp.py +0 -0
  75. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_ranges_csharp.py +0 -0
  76. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_ranges_fortran.py +0 -0
  77. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_ranges_java.py +0 -0
  78. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_ranges_js.py +0 -0
  79. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_ratchet.py +0 -0
  80. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_recurrence.py +0 -0
  81. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_runner.py +0 -0
  82. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_safe_write.py +0 -0
  83. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_scan_history.py +0 -0
  84. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_scan_view.py +0 -0
  85. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_second_source.py +0 -0
  86. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_selection.py +0 -0
  87. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_semantic.py +0 -0
  88. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_semantic_policy.py +0 -0
  89. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_semantic_ts.py +0 -0
  90. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_semantic_view.py +0 -0
  91. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_setup_errors.py +0 -0
  92. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_setup_persist.py +0 -0
  93. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_skill_data/SKILL.md +0 -0
  94. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_skill_data/agents/anthropic.yaml +0 -0
  95. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_skill_data/agents/copilot.yaml +0 -0
  96. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_skill_data/agents/openai.yaml +0 -0
  97. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_skill_data/copilot/maintainability-agent.prompt.md +0 -0
  98. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_skill_data/references/finding-taxonomy.md +0 -0
  99. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_skill_install.py +0 -0
  100. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_stored_grants.py +0 -0
  101. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_tdd_view.py +0 -0
  102. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_test_execution.py +0 -0
  103. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_test_pairing.py +0 -0
  104. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_tokens.py +0 -0
  105. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_tool_adapters.py +0 -0
  106. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_trends.py +0 -0
  107. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_user_config.py +0 -0
  108. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_verdict_adapters.py +0 -0
  109. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_verification.py +0 -0
  110. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_work_order.py +0 -0
  111. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_work_order_view.py +0 -0
  112. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_work_order_weights.py +0 -0
  113. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/_xml.py +0 -0
  114. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/baseline.py +0 -0
  115. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/cli.py +0 -0
  116. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/deadcode.py +0 -0
  117. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/duplication.py +0 -0
  118. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/evidence.py +0 -0
  119. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/git_tools.py +0 -0
  120. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/history.py +0 -0
  121. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/idioms.py +0 -0
  122. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/instructions.py +0 -0
  123. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/mcp_server.py +0 -0
  124. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/metrics.py +0 -0
  125. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/prompts.py +0 -0
  126. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/renderers.py +0 -0
  127. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/report.py +0 -0
  128. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/sarif.py +0 -0
  129. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/scoring.py +0 -0
  130. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/similarity.py +0 -0
  131. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/src/maintainability_audit/source.py +0 -0
  132. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_acquisition_trust.py +0 -0
  133. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_adapter_docs.py +0 -0
  134. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_adapter_recordings.py +0 -0
  135. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_adapters.py +0 -0
  136. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_added_lines.py +0 -0
  137. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_analysis_coverage.py +0 -0
  138. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_analysis_execution.py +0 -0
  139. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_analyzer_bridge.py +0 -0
  140. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_analyzer_config_isolation.py +0 -0
  141. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_analyzer_estimate_claims.py +0 -0
  142. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_analyzer_pinning.py +0 -0
  143. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_analyzer_primary_estimate.py +0 -0
  144. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_analyzer_provenance.py +0 -0
  145. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_analyzer_provenance_exclusions.py +0 -0
  146. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_analyzer_scoring_path.py +0 -0
  147. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_analyzer_spread_range.py +0 -0
  148. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_analyzer_xml_bounds.py +0 -0
  149. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_annotations_match_behaviour.py +0 -0
  150. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_anticipated_refusals.py +0 -0
  151. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_architecture.py +0 -0
  152. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_artifact_write_route_class.py +0 -0
  153. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_aspect_coverage.py +0 -0
  154. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_asset_snapshot_class.py +0 -0
  155. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_ast_reading_resolves_spellings.py +0 -0
  156. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_attestation.py +0 -0
  157. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_audit_components.py +0 -0
  158. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_authorization_freshness.py +0 -0
  159. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_authorship_gates.py +0 -0
  160. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_backfill.py +0 -0
  161. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_band_concealment_class.py +0 -0
  162. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_band_concealment_p3.py +0 -0
  163. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_band_pressures.py +0 -0
  164. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_bands.py +0 -0
  165. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_body_or_signature_class.py +0 -0
  166. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_c_declarations.py +0 -0
  167. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_calibration_corpus.py +0 -0
  168. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_calibration_drift.py +0 -0
  169. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_calibration_tooling.py +0 -0
  170. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_catalog_selection.py +0 -0
  171. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_catalog_tier_promise.py +0 -0
  172. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_chat_primary_docs.py +0 -0
  173. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_checkstyle_adapter.py +0 -0
  174. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_checkstyle_catalog.py +0 -0
  175. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_ci_history_recipe.py +0 -0
  176. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_ci_installs_the_analyzer_pool.py +0 -0
  177. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_cli.py +0 -0
  178. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_clone_group_class.py +0 -0
  179. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_cognitive_complexity.py +0 -0
  180. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_cohort_statistics.py +0 -0
  181. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_concealment_properties.py +0 -0
  182. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_config_shape.py +0 -0
  183. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_consent_and_grant.py +0 -0
  184. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_consumer_migration.py +0 -0
  185. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_contract_claims.py +0 -0
  186. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_corroborate.py +0 -0
  187. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_coverage_by_language.py +0 -0
  188. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_cpp_declarations.py +0 -0
  189. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_csharp_declarations.py +0 -0
  190. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_d15_composition.py +0 -0
  191. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_d15_goal_directed.py +0 -0
  192. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_dead_code.py +0 -0
  193. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_declaration_edge_cases.py +0 -0
  194. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_declaration_grading.py +0 -0
  195. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_declaration_ranges.py +0 -0
  196. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_declaration_source.py +0 -0
  197. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_declared_authorship.py +0 -0
  198. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_declared_imports.py +0 -0
  199. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_derivation_mix.py +0 -0
  200. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_determinism.py +0 -0
  201. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_dimension_ratchet.py +0 -0
  202. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_direct_runner_availability_class.py +0 -0
  203. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_discovery.py +0 -0
  204. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_doc_claims.py +0 -0
  205. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_docs_links.py +0 -0
  206. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_economic_context.py +0 -0
  207. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_empty_output_not_clean.py +0 -0
  208. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_empty_output_not_clean_adapters.py +0 -0
  209. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_environment_work_order.py +0 -0
  210. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_evidence_invariants.py +0 -0
  211. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_evidence_normalization.py +0 -0
  212. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_evidence_properties.py +0 -0
  213. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_exclusion_dialects.py +0 -0
  214. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_falsifier_standard.py +0 -0
  215. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_file_list_cap_class.py +0 -0
  216. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_finding_identity.py +0 -0
  217. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_first_run_elicitation.py +0 -0
  218. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_first_run_prompt.py +0 -0
  219. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_first_run_reelicitation.py +0 -0
  220. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_fix_breadth_window.py +0 -0
  221. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_fixed_form_fortran.py +0 -0
  222. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_format_ask.py +0 -0
  223. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_fortran_declarations.py +0 -0
  224. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_fortran_metrics.py +0 -0
  225. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_fortran_practice.py +0 -0
  226. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_git_argv.py +0 -0
  227. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_git_read_only.py +0 -0
  228. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_git_readonly_class.py +0 -0
  229. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_grant_only_user_tier.py +0 -0
  230. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_history.py +0 -0
  231. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_history_boundary.py +0 -0
  232. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_history_manifest.py +0 -0
  233. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_history_schema2.py +0 -0
  234. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_history_window.py +0 -0
  235. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_hostile_prompt.py +0 -0
  236. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_html_executive.py +0 -0
  237. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_html_section_parity_class.py +0 -0
  238. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_identity_docs.py +0 -0
  239. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_identity_resolution.py +0 -0
  240. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_idiom_divergence.py +0 -0
  241. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_installed_wheel_runtime.py +0 -0
  242. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_instruction_targets.py +0 -0
  243. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_java_range_guard.py +0 -0
  244. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_java_ranges.py +0 -0
  245. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_java_scored.py +0 -0
  246. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_java_wiring.py +0 -0
  247. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_js_complexity_operators.py +0 -0
  248. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_js_declarations.py +0 -0
  249. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_language_coverage.py +0 -0
  250. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_mcp_baseline_payload.py +0 -0
  251. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_mcp_history.py +0 -0
  252. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_migration_1_0.py +0 -0
  253. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_near_duplicates.py +0 -0
  254. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_network_disclosure.py +0 -0
  255. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_one_setup_across_surfaces_class.py +0 -0
  256. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_pattern_budget.py +0 -0
  257. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_phase6_claims.py +0 -0
  258. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_pillars_and_practice.py +0 -0
  259. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_platform_claim.py +0 -0
  260. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_pmd_adapter.py +0 -0
  261. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_pmd_audit_closures.py +0 -0
  262. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_pool_runs_by_config.py +0 -0
  263. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_practice_nested_gates_class.py +0 -0
  264. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_presentation_refusals.py +0 -0
  265. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_promises.py +0 -0
  266. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_prompt_escalation_once.py +0 -0
  267. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_prompt_honesty.py +0 -0
  268. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_prompt_location_escalation_class.py +0 -0
  269. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_prove_falsifiers.py +0 -0
  270. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_python_declarations.py +0 -0
  271. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_read_boundary.py +0 -0
  272. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_readme_claims.py +0 -0
  273. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_recurrence.py +0 -0
  274. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_release_plan.py +0 -0
  275. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_release_plan_status.py +0 -0
  276. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_report_remedy_copy.py +0 -0
  277. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_roles_recorded.py +0 -0
  278. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_root_grants.py +0 -0
  279. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_rubric_ignores_project_lint_config.py +0 -0
  280. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_runner.py +0 -0
  281. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_sarif.py +0 -0
  282. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_scan_history.py +0 -0
  283. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_scan_scope.py +0 -0
  284. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_scanning.py +0 -0
  285. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_schema_fidelity.py +0 -0
  286. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_scope_conformance.py +0 -0
  287. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_scoring_calibration.py +0 -0
  288. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_semantic_policy.py +0 -0
  289. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_setup_gate_completeness.py +0 -0
  290. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_setup_gate_independent_contract.py +0 -0
  291. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_setup_precondition.py +0 -0
  292. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_skill_install.py +0 -0
  293. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_source_index.py +0 -0
  294. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_spotbugs_adapter.py +0 -0
  295. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_spotbugs_catalog.py +0 -0
  296. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_stage8_contract.py +0 -0
  297. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_tdd_structure.py +0 -0
  298. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_test_execution_class.py +0 -0
  299. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_three_presentations.py +0 -0
  300. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_trends.py +0 -0
  301. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_type_fields_not_functions_class.py +0 -0
  302. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_uat_falsifiers.py +0 -0
  303. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_unread_code.py +0 -0
  304. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_user_config_tier.py +0 -0
  305. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_verification_audit_closures.py +0 -0
  306. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_verified_grade.py +0 -0
  307. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_version_claim.py +0 -0
  308. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_wheel_contents.py +0 -0
  309. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_work_order.py +0 -0
  310. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_work_order_items.py +0 -0
  311. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_work_order_selection.py +0 -0
  312. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_workflow_supply_chain.py +0 -0
  313. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_write_boundary.py +0 -0
  314. {maintainability_agent-2.3.0 → maintainability_agent-2.4.1}/tests/test_write_route_class.py +0 -0
  315. {maintainability_agent-2.3.0 → maintainability_agent-2.4.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.0
3
+ Version: 2.4.1
4
4
  Summary: Maintainability gate and AI remediation prompt generator for CI.
5
5
  Author: Marshall Guillory
6
6
  License: MIT
@@ -44,10 +44,10 @@ 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.3.0**.
47
+ Version **2.4.1**.
48
48
 
49
- **Languages parsed:** Python, Java, C, C++, C#, Fortran (free-form *and*
50
- fixed-form), and the JS/TS family — each by a scanner written for it, and
49
+ **Languages parsed:** Python, Java, C, C++, C#, Swift, Fortran (free-form
50
+ *and* fixed-form), and the JS/TS family — each by a scanner written for it, and
51
51
  measured with that language's own reading of what a branch is.
52
52
  [What that means per language](#language-support).
53
53
 
@@ -124,9 +124,12 @@ the independent check on their own work, so a platform that generates code and
124
124
  grades it is producing a self-assessment — a property of the arrangement, not a
125
125
  criticism of any one of them. This writes nothing and runs no model, which is
126
126
  what lets its verdict be evidence ([the principle](docs/philosophy.md#principles)).
127
- The limit, stated in the same breath: step 3 bounds the agent by **instruction**,
128
- and nothing yet verifies the returned diff stayed inside the work order
129
- ([the three checks that would](docs/roadmap.md#the-remediation-hole-an-agent-can-satisfy-this-gate-without-doing-the-work)).
127
+ Since 2.1.0 the work order is also **checked**: `--conformance` compares the
128
+ returned diff against the paths the report named, `--fail-on-regression`
129
+ ratchets the dimension scores, and `--attestation-output` composes them into one
130
+ record ([how that hole was closed](docs/roadmap.md#the-remediation-hole-closed-in-210-through-230)).
131
+ The limit, stated in the same breath: those checks read the diff's **shape**,
132
+ never its correctness — whether the change works is not a claim this tool makes.
130
133
 
131
134
  One pre-registered experiment has tested the bounded prompt.
132
135
  Generic prompting made 2 of 6 repositories worse; bounded prompting made 1 of 6 worse and improved 5 of 6, under this tool's own finding count.
@@ -217,6 +220,41 @@ withheld estimate. For PR work, add `--changed-only main...HEAD` to audit the
217
220
  diff; on a change too small to support a rate, the estimate and grade are
218
221
  withheld and the scope is named as the reason.
219
222
 
223
+ ### Checking that the agent did the work
224
+
225
+ The work order tells an agent to fix exactly these findings and refactor nothing
226
+ else. These flags check that the diff that came back obeyed it — read the
227
+ diff's _shape_, not its correctness:
228
+
229
+ ```bash
230
+ maintainability-agent \
231
+ --config maintainability-agent.json \
232
+ --conformance main...HEAD \
233
+ --fail-on-out-of-scope \
234
+ --fail-on-regression \
235
+ --attestation-output maintainability-attestation.md
236
+ ```
237
+
238
+ - **`--conformance <revspec>`** compares the diff against the paths the report
239
+ named, and answers two questions separately: did it **stay in scope**, and did
240
+ it **silence nothing**. They stay separate because a change can obey the work
241
+ order and still add a `# noqa` to a finding inside it. A test added for a fix
242
+ stays in scope even though the work order never named the test file.
243
+ - **`--fail-on-out-of-scope`** turns that into a CI failure.
244
+ - **`--fail-on-regression`** ratchets the _dimension_ scores against scan
245
+ history, catching a change that improves one dimension while silently
246
+ regressing another. It has three outcomes, not two — held, regressed, and
247
+ **not comparable**, because two scans taken under different calibration cannot
248
+ be differenced.
249
+ - **`--attestation-output`** writes the three into one per-change record: what
250
+ was measured, what the agent was told to change, whether it stayed inside the
251
+ work order, and what moved. It is reproducible and **not signed** — nothing
252
+ here holds a key, and the document says so in its own text. A check nobody ran
253
+ renders as _not asked_, never as passed.
254
+
255
+ The [roadmap](docs/roadmap.md#the-remediation-hole-closed-in-210-through-230)
256
+ has the history of why this was the hole under the product's central claim.
257
+
220
258
  ## What it analyzes
221
259
 
222
260
  The deterministic scanner reads code from your repo (no LLM calls) and produces
@@ -252,9 +290,9 @@ SonarQube, Qlty) rather than replacing them — their SARIF folds in via
252
290
  and on an unrecognized language it will quietly under-report rather than fail.**
253
291
  Coverage comes from two layers.
254
292
 
255
- Eight languages are parsed as of **1.6.0**: Python (1.0), Java (1.0), C (1.1),
256
- C++ (1.2), C# (1.3), Fortran (free-form 1.4, fixed-form 1.6), the JS/TS family,
257
- and HTML. Each has a scanner written for it and a documented list of what it
293
+ Nine languages are parsed as of **2.4.0**: Python (1.0), Java (1.0), C (1.1),
294
+ C++ (1.2), C# (1.3), Fortran (free-form 1.4, fixed-form 1.6), Swift (2.4), the
295
+ JS/TS family, and HTML. Each has a scanner written for it and a documented list of what it
258
296
  misses — a language is claimed here only when both exist.
259
297
 
260
298
  **Built-in scanner (always on, no dependencies)** — reads function/class
@@ -268,6 +306,7 @@ these:
268
306
  | C (`.c`, `.h`) | dedicated brace-bounded scanner — functions, `struct`/`enum`/`union` | Bounded; prototypes and macros are not declarations |
269
307
  | C++ (`.cpp`, `.hpp`, `.cc`, `.cxx`, `.hh`) | dedicated brace-bounded scanner — functions, class members, namespaces, templates | Bounded; bodyless declarations are not definitions |
270
308
  | C# (`.cs`) | dedicated brace-bounded scanner — methods, constructors, `class`/`interface`/`struct`/`record`/`enum` | Bounded; properties are not declarations |
309
+ | Swift (`.swift`) | dedicated brace-bounded scanner — functions, initialisers, subscripts, `class`/`struct`/`enum`/`protocol`/`actor` | Bounded; extension members carry their type, protocol requirements and computed properties are not declarations |
271
310
  | Fortran, free-form (`.f90`, `.f95`, `.f03`, `.f08`, `.F90`, `.F95`, `.F03`, `.F08`, `.pf`) | dedicated **keyword**-bounded scanner — modules, subroutines, functions, derived types | Bounded by `end`; measured with Fortran's own branch and nesting reading |
272
311
  | Fortran, fixed-form (`.f`, `.for`, `.ftn`, `.F`, `.FOR`, `.FTN`) | the same scanner over card-column source; continuations joined, labelled `DO` loops understood | Bounded by `end` or by the loop's label |
273
312
  | JS / TS / JSX / TSX (`.js`, `.jsx`, `.mjs`, `.cjs`, `.ts`, `.tsx`) | brace/paren depth over a masked copy | Bounded by the declaration's own braces |
@@ -5,10 +5,10 @@
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.3.0**.
8
+ Version **2.4.1**.
9
9
 
10
- **Languages parsed:** Python, Java, C, C++, C#, Fortran (free-form *and*
11
- fixed-form), and the JS/TS family — each by a scanner written for it, and
10
+ **Languages parsed:** Python, Java, C, C++, C#, Swift, Fortran (free-form
11
+ *and* fixed-form), and the JS/TS family — each by a scanner written for it, and
12
12
  measured with that language's own reading of what a branch is.
13
13
  [What that means per language](#language-support).
14
14
 
@@ -85,9 +85,12 @@ the independent check on their own work, so a platform that generates code and
85
85
  grades it is producing a self-assessment — a property of the arrangement, not a
86
86
  criticism of any one of them. This writes nothing and runs no model, which is
87
87
  what lets its verdict be evidence ([the principle](docs/philosophy.md#principles)).
88
- The limit, stated in the same breath: step 3 bounds the agent by **instruction**,
89
- and nothing yet verifies the returned diff stayed inside the work order
90
- ([the three checks that would](docs/roadmap.md#the-remediation-hole-an-agent-can-satisfy-this-gate-without-doing-the-work)).
88
+ Since 2.1.0 the work order is also **checked**: `--conformance` compares the
89
+ returned diff against the paths the report named, `--fail-on-regression`
90
+ ratchets the dimension scores, and `--attestation-output` composes them into one
91
+ record ([how that hole was closed](docs/roadmap.md#the-remediation-hole-closed-in-210-through-230)).
92
+ The limit, stated in the same breath: those checks read the diff's **shape**,
93
+ never its correctness — whether the change works is not a claim this tool makes.
91
94
 
92
95
  One pre-registered experiment has tested the bounded prompt.
93
96
  Generic prompting made 2 of 6 repositories worse; bounded prompting made 1 of 6 worse and improved 5 of 6, under this tool's own finding count.
@@ -178,6 +181,41 @@ withheld estimate. For PR work, add `--changed-only main...HEAD` to audit the
178
181
  diff; on a change too small to support a rate, the estimate and grade are
179
182
  withheld and the scope is named as the reason.
180
183
 
184
+ ### Checking that the agent did the work
185
+
186
+ The work order tells an agent to fix exactly these findings and refactor nothing
187
+ else. These flags check that the diff that came back obeyed it — read the
188
+ diff's _shape_, not its correctness:
189
+
190
+ ```bash
191
+ maintainability-agent \
192
+ --config maintainability-agent.json \
193
+ --conformance main...HEAD \
194
+ --fail-on-out-of-scope \
195
+ --fail-on-regression \
196
+ --attestation-output maintainability-attestation.md
197
+ ```
198
+
199
+ - **`--conformance <revspec>`** compares the diff against the paths the report
200
+ named, and answers two questions separately: did it **stay in scope**, and did
201
+ it **silence nothing**. They stay separate because a change can obey the work
202
+ order and still add a `# noqa` to a finding inside it. A test added for a fix
203
+ stays in scope even though the work order never named the test file.
204
+ - **`--fail-on-out-of-scope`** turns that into a CI failure.
205
+ - **`--fail-on-regression`** ratchets the _dimension_ scores against scan
206
+ history, catching a change that improves one dimension while silently
207
+ regressing another. It has three outcomes, not two — held, regressed, and
208
+ **not comparable**, because two scans taken under different calibration cannot
209
+ be differenced.
210
+ - **`--attestation-output`** writes the three into one per-change record: what
211
+ was measured, what the agent was told to change, whether it stayed inside the
212
+ work order, and what moved. It is reproducible and **not signed** — nothing
213
+ here holds a key, and the document says so in its own text. A check nobody ran
214
+ renders as _not asked_, never as passed.
215
+
216
+ The [roadmap](docs/roadmap.md#the-remediation-hole-closed-in-210-through-230)
217
+ has the history of why this was the hole under the product's central claim.
218
+
181
219
  ## What it analyzes
182
220
 
183
221
  The deterministic scanner reads code from your repo (no LLM calls) and produces
@@ -213,9 +251,9 @@ SonarQube, Qlty) rather than replacing them — their SARIF folds in via
213
251
  and on an unrecognized language it will quietly under-report rather than fail.**
214
252
  Coverage comes from two layers.
215
253
 
216
- Eight languages are parsed as of **1.6.0**: Python (1.0), Java (1.0), C (1.1),
217
- C++ (1.2), C# (1.3), Fortran (free-form 1.4, fixed-form 1.6), the JS/TS family,
218
- and HTML. Each has a scanner written for it and a documented list of what it
254
+ Nine languages are parsed as of **2.4.0**: Python (1.0), Java (1.0), C (1.1),
255
+ C++ (1.2), C# (1.3), Fortran (free-form 1.4, fixed-form 1.6), Swift (2.4), the
256
+ JS/TS family, and HTML. Each has a scanner written for it and a documented list of what it
219
257
  misses — a language is claimed here only when both exist.
220
258
 
221
259
  **Built-in scanner (always on, no dependencies)** — reads function/class
@@ -229,6 +267,7 @@ these:
229
267
  | C (`.c`, `.h`) | dedicated brace-bounded scanner — functions, `struct`/`enum`/`union` | Bounded; prototypes and macros are not declarations |
230
268
  | C++ (`.cpp`, `.hpp`, `.cc`, `.cxx`, `.hh`) | dedicated brace-bounded scanner — functions, class members, namespaces, templates | Bounded; bodyless declarations are not definitions |
231
269
  | C# (`.cs`) | dedicated brace-bounded scanner — methods, constructors, `class`/`interface`/`struct`/`record`/`enum` | Bounded; properties are not declarations |
270
+ | Swift (`.swift`) | dedicated brace-bounded scanner — functions, initialisers, subscripts, `class`/`struct`/`enum`/`protocol`/`actor` | Bounded; extension members carry their type, protocol requirements and computed properties are not declarations |
232
271
  | Fortran, free-form (`.f90`, `.f95`, `.f03`, `.f08`, `.F90`, `.F95`, `.F03`, `.F08`, `.pf`) | dedicated **keyword**-bounded scanner — modules, subroutines, functions, derived types | Bounded by `end`; measured with Fortran's own branch and nesting reading |
233
272
  | Fortran, fixed-form (`.f`, `.for`, `.ftn`, `.F`, `.FOR`, `.FTN`) | the same scanner over card-column source; continuations joined, labelled `DO` loops understood | Bounded by `end` or by the loop's label |
234
273
  | JS / TS / JSX / TSX (`.js`, `.jsx`, `.mjs`, `.cjs`, `.ts`, `.tsx`) | brace/paren depth over a masked copy | Bounded by the declaration's own braces |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "maintainability-agent"
7
- version = "2.3.0"
7
+ version = "2.4.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.0
3
+ Version: 2.4.1
4
4
  Summary: Maintainability gate and AI remediation prompt generator for CI.
5
5
  Author: Marshall Guillory
6
6
  License: MIT
@@ -44,10 +44,10 @@ 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.3.0**.
47
+ Version **2.4.1**.
48
48
 
49
- **Languages parsed:** Python, Java, C, C++, C#, Fortran (free-form *and*
50
- fixed-form), and the JS/TS family — each by a scanner written for it, and
49
+ **Languages parsed:** Python, Java, C, C++, C#, Swift, Fortran (free-form
50
+ *and* fixed-form), and the JS/TS family — each by a scanner written for it, and
51
51
  measured with that language's own reading of what a branch is.
52
52
  [What that means per language](#language-support).
53
53
 
@@ -124,9 +124,12 @@ the independent check on their own work, so a platform that generates code and
124
124
  grades it is producing a self-assessment — a property of the arrangement, not a
125
125
  criticism of any one of them. This writes nothing and runs no model, which is
126
126
  what lets its verdict be evidence ([the principle](docs/philosophy.md#principles)).
127
- The limit, stated in the same breath: step 3 bounds the agent by **instruction**,
128
- and nothing yet verifies the returned diff stayed inside the work order
129
- ([the three checks that would](docs/roadmap.md#the-remediation-hole-an-agent-can-satisfy-this-gate-without-doing-the-work)).
127
+ Since 2.1.0 the work order is also **checked**: `--conformance` compares the
128
+ returned diff against the paths the report named, `--fail-on-regression`
129
+ ratchets the dimension scores, and `--attestation-output` composes them into one
130
+ record ([how that hole was closed](docs/roadmap.md#the-remediation-hole-closed-in-210-through-230)).
131
+ The limit, stated in the same breath: those checks read the diff's **shape**,
132
+ never its correctness — whether the change works is not a claim this tool makes.
130
133
 
131
134
  One pre-registered experiment has tested the bounded prompt.
132
135
  Generic prompting made 2 of 6 repositories worse; bounded prompting made 1 of 6 worse and improved 5 of 6, under this tool's own finding count.
@@ -217,6 +220,41 @@ withheld estimate. For PR work, add `--changed-only main...HEAD` to audit the
217
220
  diff; on a change too small to support a rate, the estimate and grade are
218
221
  withheld and the scope is named as the reason.
219
222
 
223
+ ### Checking that the agent did the work
224
+
225
+ The work order tells an agent to fix exactly these findings and refactor nothing
226
+ else. These flags check that the diff that came back obeyed it — read the
227
+ diff's _shape_, not its correctness:
228
+
229
+ ```bash
230
+ maintainability-agent \
231
+ --config maintainability-agent.json \
232
+ --conformance main...HEAD \
233
+ --fail-on-out-of-scope \
234
+ --fail-on-regression \
235
+ --attestation-output maintainability-attestation.md
236
+ ```
237
+
238
+ - **`--conformance <revspec>`** compares the diff against the paths the report
239
+ named, and answers two questions separately: did it **stay in scope**, and did
240
+ it **silence nothing**. They stay separate because a change can obey the work
241
+ order and still add a `# noqa` to a finding inside it. A test added for a fix
242
+ stays in scope even though the work order never named the test file.
243
+ - **`--fail-on-out-of-scope`** turns that into a CI failure.
244
+ - **`--fail-on-regression`** ratchets the _dimension_ scores against scan
245
+ history, catching a change that improves one dimension while silently
246
+ regressing another. It has three outcomes, not two — held, regressed, and
247
+ **not comparable**, because two scans taken under different calibration cannot
248
+ be differenced.
249
+ - **`--attestation-output`** writes the three into one per-change record: what
250
+ was measured, what the agent was told to change, whether it stayed inside the
251
+ work order, and what moved. It is reproducible and **not signed** — nothing
252
+ here holds a key, and the document says so in its own text. A check nobody ran
253
+ renders as _not asked_, never as passed.
254
+
255
+ The [roadmap](docs/roadmap.md#the-remediation-hole-closed-in-210-through-230)
256
+ has the history of why this was the hole under the product's central claim.
257
+
220
258
  ## What it analyzes
221
259
 
222
260
  The deterministic scanner reads code from your repo (no LLM calls) and produces
@@ -252,9 +290,9 @@ SonarQube, Qlty) rather than replacing them — their SARIF folds in via
252
290
  and on an unrecognized language it will quietly under-report rather than fail.**
253
291
  Coverage comes from two layers.
254
292
 
255
- Eight languages are parsed as of **1.6.0**: Python (1.0), Java (1.0), C (1.1),
256
- C++ (1.2), C# (1.3), Fortran (free-form 1.4, fixed-form 1.6), the JS/TS family,
257
- and HTML. Each has a scanner written for it and a documented list of what it
293
+ Nine languages are parsed as of **2.4.0**: Python (1.0), Java (1.0), C (1.1),
294
+ C++ (1.2), C# (1.3), Fortran (free-form 1.4, fixed-form 1.6), Swift (2.4), the
295
+ JS/TS family, and HTML. Each has a scanner written for it and a documented list of what it
258
296
  misses — a language is claimed here only when both exist.
259
297
 
260
298
  **Built-in scanner (always on, no dependencies)** — reads function/class
@@ -268,6 +306,7 @@ these:
268
306
  | C (`.c`, `.h`) | dedicated brace-bounded scanner — functions, `struct`/`enum`/`union` | Bounded; prototypes and macros are not declarations |
269
307
  | C++ (`.cpp`, `.hpp`, `.cc`, `.cxx`, `.hh`) | dedicated brace-bounded scanner — functions, class members, namespaces, templates | Bounded; bodyless declarations are not definitions |
270
308
  | C# (`.cs`) | dedicated brace-bounded scanner — methods, constructors, `class`/`interface`/`struct`/`record`/`enum` | Bounded; properties are not declarations |
309
+ | Swift (`.swift`) | dedicated brace-bounded scanner — functions, initialisers, subscripts, `class`/`struct`/`enum`/`protocol`/`actor` | Bounded; extension members carry their type, protocol requirements and computed properties are not declarations |
271
310
  | Fortran, free-form (`.f90`, `.f95`, `.f03`, `.f08`, `.F90`, `.F95`, `.F03`, `.F08`, `.pf`) | dedicated **keyword**-bounded scanner — modules, subroutines, functions, derived types | Bounded by `end`; measured with Fortran's own branch and nesting reading |
272
311
  | Fortran, fixed-form (`.f`, `.for`, `.ftn`, `.F`, `.FOR`, `.FTN`) | the same scanner over card-column source; continuations joined, labelled `DO` loops understood | Bounded by `end` or by the loop's label |
273
312
  | JS / TS / JSX / TSX (`.js`, `.jsx`, `.mjs`, `.cjs`, `.ts`, `.tsx`) | brace/paren depth over a masked copy | Bounded by the declaration's own braces |
@@ -64,6 +64,7 @@ src/maintainability_audit/_ranges_csharp.py
64
64
  src/maintainability_audit/_ranges_fortran.py
65
65
  src/maintainability_audit/_ranges_java.py
66
66
  src/maintainability_audit/_ranges_js.py
67
+ src/maintainability_audit/_ranges_swift.py
67
68
  src/maintainability_audit/_ratchet.py
68
69
  src/maintainability_audit/_recurrence.py
69
70
  src/maintainability_audit/_runner.py
@@ -289,6 +290,8 @@ tests/test_source_index.py
289
290
  tests/test_spotbugs_adapter.py
290
291
  tests/test_spotbugs_catalog.py
291
292
  tests/test_stage8_contract.py
293
+ tests/test_suite_isolation.py
294
+ tests/test_swift_declarations.py
292
295
  tests/test_tdd_structure.py
293
296
  tests/test_test_execution_class.py
294
297
  tests/test_three_presentations.py
@@ -1,3 +1,3 @@
1
1
  """Maintainability Audit CI."""
2
2
 
3
- __version__ = "2.3.0"
3
+ __version__ = "2.4.1"
@@ -45,6 +45,14 @@ _NESTING_NODES = (ast.If, ast.For, ast.AsyncFor, ast.While, ast.ExceptHandler, a
45
45
  _NESTING_ONLY = (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda)
46
46
 
47
47
  _CONTROL_RE = re.compile(r"\b(if|for|while|catch|switch|case|elif|except)\b")
48
+ #: Swift adds `guard` — an early exit that is its primary branching idiom —
49
+ #: and `repeat`, its do-while. Without them a function built from guards
50
+ #: reads as having no branches at all, which is the Fortran `do` defect in
51
+ #: another language: six nested loops once scored complexity 1 because the
52
+ #: pattern did not know the keyword.
53
+ _SWIFT_CONTROL_RE = re.compile(
54
+ r"\b(if|for|while|catch|switch|case|guard|repeat)\b"
55
+ )
48
56
  _ELSE_RE = re.compile(r"\belse\b")
49
57
  _BOOLEAN_RE = re.compile(r"&&|\|\|")
50
58
 
@@ -167,7 +175,7 @@ def fortran_cognitive(lines: list[str]) -> int:
167
175
  return score
168
176
 
169
177
 
170
- def brace_cognitive(lines: list[str]) -> int:
178
+ def brace_cognitive(lines: list[str], control: re.Pattern[str] | None = None) -> int:
171
179
  """Approximate cognitive complexity for a C-family declaration body.
172
180
 
173
181
  Nesting is read from brace depth relative to the declaration's own
@@ -191,16 +199,25 @@ def brace_cognitive(lines: list[str]) -> int:
191
199
  if base is None and opens:
192
200
  base = depth
193
201
  nesting = max(0, depth - (base if base is not None else 0))
194
- score += _score_brace_line(line, nesting)
202
+ score += _score_brace_line(line, nesting, control or _CONTROL_RE)
195
203
  depth = max(0, depth + opens - closes)
196
204
  return score
197
205
 
198
206
 
199
- def _score_brace_line(line: str, nesting: int) -> int:
207
+ def _score_brace_line(line: str, nesting: int, control: re.Pattern[str]) -> int:
200
208
  score = 0
201
- for _ in _CONTROL_RE.finditer(line):
209
+ for _ in control.finditer(line):
202
210
  score += 1 + nesting
203
211
  if _ELSE_RE.search(line):
204
212
  score += 1
205
213
  score += len(_BOOLEAN_RE.findall(line))
206
214
  return score
215
+
216
+
217
+ def swift_cognitive(lines: list[str]) -> int:
218
+ """`brace_cognitive` reading Swift's control keywords.
219
+
220
+ Swift is braced, so nesting is read the same way; only the vocabulary
221
+ differs, and `guard` is the difference that matters.
222
+ """
223
+ return brace_cognitive(lines, _SWIFT_CONTROL_RE)
@@ -45,6 +45,7 @@ DEFAULT_CONFIG: dict[str, Any] = {
45
45
  ".py", ".java", ".c", ".h",
46
46
  ".cpp", ".hpp", ".cc", ".cxx", ".hh",
47
47
  ".cs",
48
+ ".swift",
48
49
  ".f90", ".f95", ".f03", ".f08",
49
50
  ".F90", ".F95", ".F03", ".F08", ".pf",
50
51
  ".f", ".for", ".ftn", ".F", ".FOR", ".FTN",
@@ -53,6 +53,20 @@ COMPLEXITY_RE = re.compile(
53
53
  )
54
54
 
55
55
 
56
+ #: Swift's `guard` is an early exit and its primary branching idiom, and
57
+ #: `repeat` is its do-while. Neither is in the C-family pattern, so a
58
+ #: guard-heavy function would read as branchless — the same defect Fortran
59
+ #: had with `do`.
60
+ SWIFT_COMPLEXITY_RE = re.compile(
61
+ r"\b(if|for|while|case|catch|guard|repeat)\b|&&|\|\||\?\?|\?(?![.?])"
62
+ )
63
+
64
+
65
+ def swift_branch_points(line: str) -> int:
66
+ """Decision points on one line of Swift."""
67
+ return len(SWIFT_COMPLEXITY_RE.findall(line))
68
+
69
+
56
70
  def branch_points(line: str) -> int:
57
71
  """Decision points on one line, for the C family and Python."""
58
72
  return len(COMPLEXITY_RE.findall(line))
@@ -0,0 +1,215 @@
1
+ """Declaration ranges for Swift.
2
+
3
+ Braced, so it reuses `scan_bounded` with C, C++, C# and Java. What is
4
+ different is worth stating, because two of the differences are the whole
5
+ job and one of them is a trap this project has already fallen into once.
6
+
7
+ **Every declaration is keyword-led.** `func`, `init`, `subscript`, `class`,
8
+ `struct`, `enum`, `protocol`, `actor`, `extension`. That makes recognition
9
+ *easier* than C++, where a bare `name(` is equally a call, a macro and a
10
+ constructor, and where a wrong guess let a macro swallow the next
11
+ declaration's braces. Nothing here has to guess.
12
+
13
+ **An `extension` adds members to a type declared elsewhere**, often in
14
+ another file. `extension Widget { func draw() }` must report `Widget.draw`
15
+ rather than a second bare `draw` indistinguishable from every other type's,
16
+ which is the same rule C++ applies to an out-of-line `void geo::Widget::draw()`.
17
+ The extension itself is walked into and not graded: it is a container, and
18
+ grading it would count its members twice.
19
+
20
+ **Computed properties are not declarations**, and this is the C# properties
21
+ problem in Swift spelling. `var area: Double { w * h }` has braces and would
22
+ bound perfectly cleanly; an ordinary Swift type has many, each a line or
23
+ two, none anybody's maintenance burden. Counting them would dilute the
24
+ population every declaration rate divides by. They are excluded by
25
+ construction rather than by a rule: every pattern here requires either a
26
+ parameter list or a type keyword, and a property has neither.
27
+
28
+ **A protocol requirement has no body** — `func draw()` with nothing after
29
+ it — so it mints nothing, exactly as a C prototype and a pure virtual do.
30
+ A protocol of forty requirements is not forty declarations.
31
+
32
+ One claim was corrected while writing this. The roadmap said Swift's string
33
+ interpolation "puts braces inside literals, so masking has to run before
34
+ depth counting". It does not: Swift interpolates with `\\(expr)` —
35
+ parentheses, not braces — and string contents are masked before any
36
+ counting regardless. The real reason masking matters here is the ordinary
37
+ one every language shares: a brace inside a string literal, `"{}"`, would
38
+ desync depth.
39
+ """
40
+ from __future__ import annotations
41
+
42
+ import re
43
+
44
+ from ._metrics_types import DeclRange
45
+ from ._ranges_core import _mask_generics, scan_bounded
46
+
47
+ # Stripped once per line so the patterns stay small: these lead a
48
+ # declaration and say nothing about what it is.
49
+ _SW_MODIFIER_RE = re.compile(
50
+ r"^\s*(?:(?:public|private|internal|fileprivate|open|static|final"
51
+ r"|override|mutating|nonmutating|convenience|required|lazy|weak|unowned"
52
+ r"|indirect|dynamic|optional|async|throws|rethrows|infix|prefix|postfix"
53
+ r"|package)\s+)*"
54
+ )
55
+ # `class` is both a declaration keyword and a modifier: `class Widget` is a
56
+ # type, `class func make()` is a type-level method. Stripping it with the
57
+ # other modifiers cost every `final class Store` its keyword — the type
58
+ # vanished and only its members were reported. So it is removed only where
59
+ # a declaration keyword follows it, and left alone otherwise.
60
+ _SW_CLASS_MODIFIER_RE = re.compile(r"^class\s+(?=(?:func|var|let|subscript)\b)")
61
+ # `@objc`, `@available(iOS 13, *)`, `@MainActor` — an attribute leads a
62
+ # declaration and is not one. Stripped, so what follows is still read.
63
+ _SW_ATTRIBUTE_RE = re.compile(r"^\s*(?:@[A-Za-z_]\w*(?:\([^)]*\))?\s*)+")
64
+ # `extension Widget`, `extension Widget: Drawable`, `extension Array where …`
65
+ _SW_EXTENSION_RE = re.compile(r"^extension\s+([A-Za-z_][\w.]*)")
66
+ # The container keywords. `protocol` is here so its body is walked into —
67
+ # its requirements are then skipped for having no body, which is what makes
68
+ # a forty-requirement protocol mint nothing.
69
+ _SW_TYPE_RE = re.compile(
70
+ r"^(?:class|struct|enum|protocol|actor)\s+([A-Za-z_]\w*)"
71
+ )
72
+ # `func name(`, and the three declarations that carry no `func` keyword.
73
+ _SW_FUNC_RE = re.compile(r"^func\s+([A-Za-z_]\w*|[^\s(]+)\s*(?:<[^>]*>)?\s*\(")
74
+ _SW_INIT_RE = re.compile(r"^(init\??|deinit|subscript)\b")
75
+
76
+
77
+ def _swift_declaration(text: str) -> tuple[str, str | None] | None:
78
+ """``(name, kind)`` for a Swift declaration on one masked line.
79
+
80
+ ``kind`` is ``None`` for an `extension`: walk in, grade nothing.
81
+ """
82
+ line = _SW_ATTRIBUTE_RE.sub("", _mask_generics(text), count=1)
83
+ body = line[_SW_MODIFIER_RE.match(line).end():]
84
+ body = _SW_CLASS_MODIFIER_RE.sub("", body, count=1)
85
+ body = body[_SW_MODIFIER_RE.match(body).end():]
86
+
87
+ extension = _SW_EXTENSION_RE.match(body)
88
+ if extension is not None:
89
+ # The extended type's name, carried so its members can be reported
90
+ # as `Widget.draw` rather than as a second bare `draw`.
91
+ return extension.group(1), None
92
+
93
+ type_match = _SW_TYPE_RE.match(body)
94
+ if type_match is not None:
95
+ return type_match.group(1), "class"
96
+
97
+ func = _SW_FUNC_RE.match(body)
98
+ if func is not None:
99
+ return (func.group(1), "function") if _opens_a_body(body) else None
100
+
101
+ special = _SW_INIT_RE.match(body)
102
+ if special is not None:
103
+ return (special.group(1).rstrip("?"), "function") if _opens_a_body(body) else None
104
+
105
+ # Anything else — `var`, `let`, a computed property, a `case` — is not
106
+ # a declaration this tool measures. Falling through rather than listing
107
+ # exclusions keeps the rule "a declaration is keyword-led and named".
108
+ return None
109
+
110
+
111
+ def _opens_a_body(body: str) -> bool:
112
+ """Whether this line starts a body, or is a requirement with none.
113
+
114
+ Swift has no statement terminator, so the shared `skip_bare` check
115
+ cannot work here: it looks for a signature that closes without opening
116
+ a brace, and in C or C# that is a `;`. A Swift protocol requirement
117
+ simply ends at the newline, so the end-finder walks on and adopts the
118
+ *next* declaration's brace — which is how `func describe() -> String`
119
+ inside a protocol came back measured as two lines of body.
120
+
121
+ The rule that replaces it: a signature whose parentheses balance on
122
+ this line and which then shows no `{` has no body. A wrapped signature
123
+ leaves its parentheses unbalanced, so it is still read as a
124
+ declaration and its body is found normally:
125
+
126
+ func draw( <- unbalanced here, kept
127
+ into: Canvas
128
+ ) throws {
129
+
130
+ A body written Allman — `func f()` with `{` alone on the next line —
131
+ is missed and mints nothing. That is legal Swift and vanishingly rare
132
+ in it, and missing one declaration is the cheaper error than counting
133
+ every protocol requirement in the tree.
134
+ """
135
+ if "{" in body:
136
+ return True
137
+ return body.count("(") != body.count(")")
138
+
139
+
140
+ def swift_declaration_ranges(lines: list[str]) -> tuple[list[DeclRange], list[str]]:
141
+ """Functions, initialisers and types, each bounded by its own body.
142
+
143
+ Types and extensions are descended into, because that is where members
144
+ live; a function body is stepped over, so nothing inside one is read as
145
+ a declaration.
146
+
147
+ Bodyless declarations mint nothing, as in C, C++ and C#: a protocol
148
+ requirement is a signature with nothing to measure, and grading it
149
+ would put one-line members into the population every rate divides by.
150
+
151
+ Everything it misses, it misses in the safe direction:
152
+
153
+ - **Computed properties** (`var area: Double { w * h }`) are not
154
+ declarations, the same call C# makes for the same reason.
155
+ - **A closure assigned to a property** is a body without a declaration
156
+ keyword and is not counted.
157
+ - **Declarations produced by a macro** (`@freestanding`, `#`-macros)
158
+ are not in the source and are not seen, as in C and C++.
159
+ - **Conditional compilation is not evaluated**, so a declaration in a
160
+ disabled `#if` arm still counts.
161
+ """
162
+ ranges, masked = scan_bounded(
163
+ lines, _swift_declaration, descend=("class",), skip_preprocessor=True
164
+ )
165
+ return _qualify_extension_members(ranges, masked), masked
166
+
167
+
168
+ def _extension_spans(masked: list[str]) -> list[tuple[int, int, str]]:
169
+ """Every `extension X { … }` as (start, end, extended type)."""
170
+ def only_extensions(text: str) -> tuple[str, str | None] | None:
171
+ found = _swift_declaration(text)
172
+ if found is None:
173
+ return None
174
+ name, kind = found
175
+ return (name, "class") if kind is None else None
176
+
177
+ spans, _ = scan_bounded(
178
+ masked, only_extensions, descend=(), skip_preprocessor=True
179
+ )
180
+ return [(item.start, item.end, item.name) for item in spans]
181
+
182
+
183
+ def _qualify_extension_members(
184
+ ranges: list[DeclRange], masked: list[str]
185
+ ) -> list[DeclRange]:
186
+ """Report an extension's members under the type they extend.
187
+
188
+ `extension Widget { func draw() }` becomes `Widget.draw`. Without it
189
+ the report carries a bare `draw` indistinguishable from every other
190
+ type's `draw`, and a reader given "shorten `draw`" cannot find which
191
+ one — the same reason C++ keeps the qualification on an out-of-line
192
+ `void geo::Widget::draw()`, except that in Swift the source does not
193
+ write it and the scanner has to carry it.
194
+
195
+ A member already qualified is left alone, and a nested type inside an
196
+ extension keeps its own name: the prefix answers "which type does this
197
+ belong to", which a named type already answers for itself.
198
+ """
199
+ spans = _extension_spans(masked)
200
+ if not spans:
201
+ return ranges
202
+ qualified = []
203
+ for item in ranges:
204
+ owner = next(
205
+ (name for start, end, name in spans
206
+ if start < item.start <= end and item.kind == "function"),
207
+ None,
208
+ )
209
+ if owner is None or "." in item.name:
210
+ qualified.append(item)
211
+ continue
212
+ qualified.append(
213
+ DeclRange(item.start, item.end, f"{owner}.{item.name}", item.kind)
214
+ )
215
+ return qualified
@@ -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.3.0"
14
+ VERSION = "2.4.1"
15
15
 
16
16
  PROJECT_URL = "https://github.com/marshallguillory86/maintainability-agent"
17
17