maintainability-agent 2.8.0__tar.gz → 2.9.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 (332) hide show
  1. {maintainability_agent-2.8.0/src/maintainability_agent.egg-info → maintainability_agent-2.9.0}/PKG-INFO +50 -56
  2. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/README.md +49 -55
  3. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/pyproject.toml +1 -1
  4. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0/src/maintainability_agent.egg-info}/PKG-INFO +50 -56
  5. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_agent.egg-info/SOURCES.txt +6 -0
  6. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/__init__.py +1 -1
  7. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_arguments.py +23 -0
  8. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_conformance.py +51 -9
  9. maintainability_agent-2.9.0/src/maintainability_audit/_precommit.py +259 -0
  10. maintainability_agent-2.9.0/src/maintainability_audit/_precommit_install.py +229 -0
  11. maintainability_agent-2.9.0/src/maintainability_audit/_precommit_view.py +80 -0
  12. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_run_comparison.py +9 -3
  13. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_safe_write.py +46 -1
  14. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/baseline.py +5 -1
  15. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/cli.py +70 -0
  16. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/config.py +67 -2
  17. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/git_tools.py +88 -2
  18. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_analyzer_estimate_claims.py +5 -0
  19. maintainability_agent-2.9.0/tests/test_anchor_and_mode.py +139 -0
  20. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_authorship_gates.py +36 -0
  21. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_evidence_properties.py +3 -1
  22. maintainability_agent-2.9.0/tests/test_operator_named_paths.py +113 -0
  23. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_platform_claim.py +25 -15
  24. maintainability_agent-2.9.0/tests/test_precommit_staged.py +372 -0
  25. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_prove_falsifiers.py +61 -0
  26. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_readme_claims.py +134 -0
  27. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_scope_conformance.py +52 -0
  28. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_three_presentations.py +3 -1
  29. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_tree_chosen_spawn.py +76 -27
  30. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_write_route_class.py +15 -2
  31. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/LICENSE +0 -0
  32. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/setup.cfg +0 -0
  33. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_agent.egg-info/dependency_links.txt +0 -0
  34. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_agent.egg-info/entry_points.txt +0 -0
  35. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_agent.egg-info/requires.txt +0 -0
  36. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_agent.egg-info/top_level.txt +0 -0
  37. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/__main__.py +0 -0
  38. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_adapters.py +0 -0
  39. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_analysis.py +0 -0
  40. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_anchor.py +0 -0
  41. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_aspects.py +0 -0
  42. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_assets/analyzer-catalog.json +0 -0
  43. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_assets/standard.md +0 -0
  44. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_attestation.py +0 -0
  45. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_backfill.py +0 -0
  46. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_bands.py +0 -0
  47. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_built_ins.py +0 -0
  48. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_calibration.py +0 -0
  49. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_catalog.py +0 -0
  50. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_charts.py +0 -0
  51. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_cognitive.py +0 -0
  52. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_config_defaults.py +0 -0
  53. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_corroborate.py +0 -0
  54. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_coverage_notes.py +0 -0
  55. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_derive.py +0 -0
  56. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_discovery.py +0 -0
  57. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_documents.py +0 -0
  58. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_economics.py +0 -0
  59. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_economics_view.py +0 -0
  60. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_environment.py +0 -0
  61. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_evidence_view.py +0 -0
  62. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_finding_match.py +0 -0
  63. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_first_run.py +0 -0
  64. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_formula.py +0 -0
  65. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_gates.py +0 -0
  66. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_generic.py +0 -0
  67. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_grant_ledger.py +0 -0
  68. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_history_view.py +0 -0
  69. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_hostile_prompt.py +0 -0
  70. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_hotspots.py +0 -0
  71. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_html_report_sections.py +0 -0
  72. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_html_view.py +0 -0
  73. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_identity.py +0 -0
  74. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_jvm_adapters.py +0 -0
  75. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_markdown_sections.py +0 -0
  76. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_masking.py +0 -0
  77. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_mcp_audit.py +0 -0
  78. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_mcp_gate.py +0 -0
  79. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_mcp_grants.py +0 -0
  80. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_mcp_refusals.py +0 -0
  81. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_mcp_resources.py +0 -0
  82. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_mcp_setup.py +0 -0
  83. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_metric_adapters.py +0 -0
  84. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_metrics_types.py +0 -0
  85. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_pillars.py +0 -0
  86. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_practice.py +0 -0
  87. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_pressures.py +0 -0
  88. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_ranges_c.py +0 -0
  89. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_ranges_cobol.py +0 -0
  90. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_ranges_core.py +0 -0
  91. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_ranges_cpp.py +0 -0
  92. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_ranges_csharp.py +0 -0
  93. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_ranges_fortran.py +0 -0
  94. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_ranges_java.py +0 -0
  95. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_ranges_js.py +0 -0
  96. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_ranges_swift.py +0 -0
  97. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_ratchet.py +0 -0
  98. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_recurrence.py +0 -0
  99. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_runner.py +0 -0
  100. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_scan_history.py +0 -0
  101. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_scan_view.py +0 -0
  102. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_second_source.py +0 -0
  103. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_selection.py +0 -0
  104. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_semantic.py +0 -0
  105. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_semantic_policy.py +0 -0
  106. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_semantic_ts.py +0 -0
  107. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_semantic_view.py +0 -0
  108. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_setup_errors.py +0 -0
  109. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_setup_persist.py +0 -0
  110. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_skill_data/SKILL.md +0 -0
  111. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_skill_data/agents/anthropic.yaml +0 -0
  112. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_skill_data/agents/copilot.yaml +0 -0
  113. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_skill_data/agents/openai.yaml +0 -0
  114. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_skill_data/copilot/maintainability-agent.prompt.md +0 -0
  115. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_skill_data/references/finding-taxonomy.md +0 -0
  116. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_skill_install.py +0 -0
  117. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_stored_grants.py +0 -0
  118. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_tdd_view.py +0 -0
  119. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_test_commands.py +0 -0
  120. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_test_execution.py +0 -0
  121. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_test_pairing.py +0 -0
  122. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_tokens.py +0 -0
  123. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_tool_adapters.py +0 -0
  124. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_trends.py +0 -0
  125. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_user_config.py +0 -0
  126. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_verdict_adapters.py +0 -0
  127. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_verification.py +0 -0
  128. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_work_order.py +0 -0
  129. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_work_order_view.py +0 -0
  130. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_work_order_weights.py +0 -0
  131. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/_xml.py +0 -0
  132. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/deadcode.py +0 -0
  133. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/declarations.py +0 -0
  134. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/duplication.py +0 -0
  135. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/evidence.py +0 -0
  136. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/history.py +0 -0
  137. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/idioms.py +0 -0
  138. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/instructions.py +0 -0
  139. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/mcp_server.py +0 -0
  140. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/metrics.py +0 -0
  141. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/prompts.py +0 -0
  142. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/renderers.py +0 -0
  143. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/report.py +0 -0
  144. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/sarif.py +0 -0
  145. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/scoring.py +0 -0
  146. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/similarity.py +0 -0
  147. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/src/maintainability_audit/source.py +0 -0
  148. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_acquisition_trust.py +0 -0
  149. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_adapter_docs.py +0 -0
  150. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_adapter_recordings.py +0 -0
  151. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_adapters.py +0 -0
  152. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_added_lines.py +0 -0
  153. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_analysis_coverage.py +0 -0
  154. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_analysis_execution.py +0 -0
  155. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_analyzer_bridge.py +0 -0
  156. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_analyzer_config_isolation.py +0 -0
  157. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_analyzer_pinning.py +0 -0
  158. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_analyzer_primary_estimate.py +0 -0
  159. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_analyzer_provenance.py +0 -0
  160. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_analyzer_provenance_exclusions.py +0 -0
  161. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_analyzer_scoring_path.py +0 -0
  162. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_analyzer_spread_range.py +0 -0
  163. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_analyzer_xml_bounds.py +0 -0
  164. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_anchor_disclosure.py +0 -0
  165. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_annotations_match_behaviour.py +0 -0
  166. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_anticipated_refusals.py +0 -0
  167. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_architecture.py +0 -0
  168. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_artifact_write_route_class.py +0 -0
  169. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_aspect_coverage.py +0 -0
  170. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_asset_snapshot_class.py +0 -0
  171. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_ast_reading_resolves_spellings.py +0 -0
  172. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_attestation.py +0 -0
  173. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_audit_components.py +0 -0
  174. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_authorization_freshness.py +0 -0
  175. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_backfill.py +0 -0
  176. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_band_concealment_class.py +0 -0
  177. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_band_concealment_p3.py +0 -0
  178. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_band_pressures.py +0 -0
  179. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_bands.py +0 -0
  180. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_body_or_signature_class.py +0 -0
  181. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_c_declarations.py +0 -0
  182. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_calibration_corpus.py +0 -0
  183. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_calibration_drift.py +0 -0
  184. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_calibration_tooling.py +0 -0
  185. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_catalog_selection.py +0 -0
  186. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_catalog_tier_promise.py +0 -0
  187. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_chat_primary_docs.py +0 -0
  188. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_checkstyle_adapter.py +0 -0
  189. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_checkstyle_catalog.py +0 -0
  190. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_ci_history_recipe.py +0 -0
  191. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_ci_installs_the_analyzer_pool.py +0 -0
  192. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_claimed_languages.py +0 -0
  193. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_cli.py +0 -0
  194. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_clone_group_class.py +0 -0
  195. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_cobol_declarations.py +0 -0
  196. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_cognitive_complexity.py +0 -0
  197. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_cohort_statistics.py +0 -0
  198. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_concealment_properties.py +0 -0
  199. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_config_shape.py +0 -0
  200. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_consent_and_grant.py +0 -0
  201. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_consumer_migration.py +0 -0
  202. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_contract_claims.py +0 -0
  203. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_corroborate.py +0 -0
  204. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_coverage_by_language.py +0 -0
  205. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_cpp_declarations.py +0 -0
  206. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_csharp_declarations.py +0 -0
  207. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_d15_composition.py +0 -0
  208. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_d15_goal_directed.py +0 -0
  209. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_dead_code.py +0 -0
  210. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_declaration_edge_cases.py +0 -0
  211. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_declaration_grading.py +0 -0
  212. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_declaration_ranges.py +0 -0
  213. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_declaration_source.py +0 -0
  214. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_declared_authorship.py +0 -0
  215. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_declared_imports.py +0 -0
  216. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_derivation_mix.py +0 -0
  217. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_determinism.py +0 -0
  218. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_dimension_ratchet.py +0 -0
  219. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_direct_runner_availability_class.py +0 -0
  220. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_discovery.py +0 -0
  221. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_doc_claims.py +0 -0
  222. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_docs_links.py +0 -0
  223. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_economic_context.py +0 -0
  224. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_empty_output_not_clean.py +0 -0
  225. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_empty_output_not_clean_adapters.py +0 -0
  226. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_environment_work_order.py +0 -0
  227. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_evidence_invariants.py +0 -0
  228. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_evidence_normalization.py +0 -0
  229. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_exclusion_dialects.py +0 -0
  230. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_falsifier_standard.py +0 -0
  231. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_file_list_cap_class.py +0 -0
  232. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_finding_identity.py +0 -0
  233. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_first_run_elicitation.py +0 -0
  234. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_first_run_prompt.py +0 -0
  235. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_first_run_reelicitation.py +0 -0
  236. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_fix_breadth_window.py +0 -0
  237. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_fixed_form_fortran.py +0 -0
  238. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_format_ask.py +0 -0
  239. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_fortran_declarations.py +0 -0
  240. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_fortran_metrics.py +0 -0
  241. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_fortran_practice.py +0 -0
  242. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_git_argv.py +0 -0
  243. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_git_read_only.py +0 -0
  244. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_git_readonly_class.py +0 -0
  245. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_grant_only_user_tier.py +0 -0
  246. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_history.py +0 -0
  247. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_history_boundary.py +0 -0
  248. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_history_manifest.py +0 -0
  249. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_history_schema2.py +0 -0
  250. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_history_window.py +0 -0
  251. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_hostile_prompt.py +0 -0
  252. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_html_executive.py +0 -0
  253. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_html_section_parity_class.py +0 -0
  254. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_identity_docs.py +0 -0
  255. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_identity_resolution.py +0 -0
  256. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_idiom_divergence.py +0 -0
  257. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_installed_wheel_runtime.py +0 -0
  258. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_instruction_targets.py +0 -0
  259. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_java_range_guard.py +0 -0
  260. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_java_ranges.py +0 -0
  261. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_java_scored.py +0 -0
  262. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_java_wiring.py +0 -0
  263. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_js_complexity_operators.py +0 -0
  264. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_js_declarations.py +0 -0
  265. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_language_coverage.py +0 -0
  266. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_mcp_baseline_payload.py +0 -0
  267. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_mcp_history.py +0 -0
  268. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_mcp_server.py +0 -0
  269. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_migration_1_0.py +0 -0
  270. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_near_duplicates.py +0 -0
  271. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_network_disclosure.py +0 -0
  272. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_one_setup_across_surfaces_class.py +0 -0
  273. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_pattern_budget.py +0 -0
  274. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_phase6_claims.py +0 -0
  275. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_pillars_and_practice.py +0 -0
  276. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_pmd_adapter.py +0 -0
  277. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_pmd_audit_closures.py +0 -0
  278. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_pool_runs_by_config.py +0 -0
  279. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_population_floors.py +0 -0
  280. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_practice_nested_gates_class.py +0 -0
  281. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_presentation_refusals.py +0 -0
  282. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_promises.py +0 -0
  283. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_prompt_escalation_once.py +0 -0
  284. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_prompt_honesty.py +0 -0
  285. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_prompt_location_escalation_class.py +0 -0
  286. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_python_declarations.py +0 -0
  287. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_read_boundary.py +0 -0
  288. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_recurrence.py +0 -0
  289. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_release_plan.py +0 -0
  290. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_release_plan_status.py +0 -0
  291. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_report_remedy_copy.py +0 -0
  292. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_roles_recorded.py +0 -0
  293. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_root_grants.py +0 -0
  294. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_rubric_ignores_project_lint_config.py +0 -0
  295. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_run_comparison.py +0 -0
  296. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_runner.py +0 -0
  297. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_sarif.py +0 -0
  298. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_scan_history.py +0 -0
  299. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_scan_scope.py +0 -0
  300. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_scanner_unsafe_direction.py +0 -0
  301. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_scanning.py +0 -0
  302. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_schema_fidelity.py +0 -0
  303. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_scoring_calibration.py +0 -0
  304. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_semantic_policy.py +0 -0
  305. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_setup_gate_completeness.py +0 -0
  306. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_setup_gate_independent_contract.py +0 -0
  307. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_setup_precondition.py +0 -0
  308. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_skill_install.py +0 -0
  309. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_source_index.py +0 -0
  310. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_spotbugs_adapter.py +0 -0
  311. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_spotbugs_catalog.py +0 -0
  312. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_stage8_contract.py +0 -0
  313. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_suite_isolation.py +0 -0
  314. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_swift_declarations.py +0 -0
  315. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_tdd_structure.py +0 -0
  316. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_test_commands.py +0 -0
  317. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_test_execution_class.py +0 -0
  318. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_trends.py +0 -0
  319. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_type_fields_not_functions_class.py +0 -0
  320. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_uat_falsifiers.py +0 -0
  321. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_unread_code.py +0 -0
  322. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_user_config_tier.py +0 -0
  323. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_verification_audit_closures.py +0 -0
  324. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_verified_grade.py +0 -0
  325. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_version_claim.py +0 -0
  326. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_wheel_contents.py +0 -0
  327. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_work_order.py +0 -0
  328. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_work_order_items.py +0 -0
  329. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_work_order_selection.py +0 -0
  330. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_workflow_supply_chain.py +0 -0
  331. {maintainability_agent-2.8.0 → maintainability_agent-2.9.0}/tests/test_write_boundary.py +0 -0
  332. {maintainability_agent-2.8.0 → maintainability_agent-2.9.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.8.0
3
+ Version: 2.9.0
4
4
  Summary: Maintainability gate and AI remediation prompt generator for CI.
5
5
  Author: Marshall Guillory
6
6
  License: MIT
@@ -39,12 +39,12 @@ Dynamic: license-file
39
39
 
40
40
  # Maintainability Agent
41
41
 
42
- ![maintainability-agent — a deterministic audit whose output is a bounded work order for your AI coding agent: fix exactly these findings, refactor nothing else](docs/cover.png)
42
+ ![maintainability-agent — a deterministic audit whose output is a bounded work order for your AI coding agent: fix exactly these findings, refactor nothing else](https://raw.githubusercontent.com/marshallguillory86/maintainability-agent/main/docs/cover.png)
43
43
 
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.8.0**.
47
+ Version **2.9.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
@@ -201,6 +201,8 @@ integration](docs/ide-agent-integration.md).
201
201
 
202
202
  ## Automation / CI: CLI
203
203
 
204
+ ![The loop, end to end: staged content meets a fast gate that blocks or passes it, the full audit measures what landed, the work order names exactly what to fix, and the repair is checked back against it](https://raw.githubusercontent.com/marshallguillory86/maintainability-agent/main/docs/MA_workflow.jpg)
205
+
204
206
  Use the CLI for scripts, repeatable automation, and CI gates. Copy the example
205
207
  config to your repo root as `maintainability-agent.json`, then:
206
208
 
@@ -260,6 +262,30 @@ maintainability-agent \
260
262
  The [roadmap](docs/roadmap.md#the-remediation-hole-closed-in-210-through-230)
261
263
  has the history of why this was the hole under the product's central claim.
262
264
 
265
+ ### Before the commit
266
+
267
+ The gate above runs after the work is done. `--staged` runs while it is still in
268
+ the author's hands:
269
+
270
+ ```bash
271
+ maintainability-agent --install-precommit-hook # writes .git/hooks/pre-commit
272
+ ```
273
+
274
+ It scans **the index, not the working tree** — stage half a file with `git add
275
+ -p` and keep typing, and what gets measured is what the commit will actually
276
+ contain. Clean, it prints nothing and exits 0. Breaching, it names each path,
277
+ line and remedy and exits 1; `--format json` gives an agent the same findings.
278
+
279
+ It is not a small audit. It produces **no score**, because a diff has no
280
+ population to draw a rate from — only threshold breaches, including a `# noqa`
281
+ this change added. It applies no repository gates: a missing README is a
282
+ property of the repository, not of your diff. And it runs nothing and writes
283
+ nothing, so it costs 0.17s here against the full audit's 266 — a hook slower
284
+ than the author's patience is one they uninstall.
285
+
286
+ `--install-precommit-hook` refuses to replace a hook it did not write, printing
287
+ the line to add to yours instead, and honours `core.hooksPath`.
288
+
263
289
  ## What it analyzes
264
290
 
265
291
  The deterministic scanner reads code from your repo (no LLM calls) and produces
@@ -322,42 +348,16 @@ Any language **not** in that table — Go, Rust, Ruby, PHP, Kotlin, and the rest
322
348
  is **not parsed for declarations by the built-in scanner.** Its files still count
323
349
  toward repo size, but the built-ins produce no function-size, complexity,
324
350
  duplication or dead-code findings for them, and the estimate leans on whatever
325
- evidence *is* available (which is why the report discloses its evidence tier and
326
- can withhold the grade).
327
-
328
- **Which analyzer covers which language** (the opt-in pool, when installed):
329
-
330
- | Language | Built-in scanner | External analyzer |
331
- |---|---|---|
332
- | Python | `ast`, exact | ruff, radon, mypy, vulture, complexipy, interrogate, pydocstyle, pylint, cohesion |
333
- | Java | dedicated scanner | lizard, PMD, Checkstyle, SpotBugs |
334
- | C / C++ / C# | dedicated scanners | lizard, multimetric |
335
- | **Fortran** (free- and fixed-form) | dedicated scanner | **fortitude** — 100+ rules; **lizard** — complexity, NLOC, params |
336
- | **COBOL** | dedicated scanner | **none** — no offline analyzer in the catalog reads it |
337
- | JS / TS / JSX / TSX | brace scanner | ESLint, lizard, jscpd |
338
-
339
- Fortran reached parity in 1.6.0: lizard had read it for years behind a
340
- stale catalog row, so it came out `not-applicable` and never ran. A lint fails
341
- the build if a parsed language has no analyzer measuring complexity — **COBOL is
342
- the one disclosed exemption**, because the tooling that reads it is licensed and
343
- host-resident, so its external tier is empty and the report says so.
344
-
345
- **External analyzer adapters (opt-in pool)** — this is how coverage extends
346
- beyond the built-in set. When you enable the analyzer pool, the tool shells out
347
- to mature analyzers and folds their output in through per-tool
348
- [adapters](docs/adapters.md): **lizard** (cyclomatic complexity across ~a dozen
349
- languages), **jscpd** (cross-language duplication), **ESLint** (JS/TS),
350
- **PMD** / **SpotBugs** (JVM), and others in the catalog. These run only when
351
- selected *and* installed (acquisition is opt-in and off by default), and where
352
- they measured a full concept set they become the *primary* evidence, with the
353
- built-ins as the fallback.
351
+ evidence *is* available — which is why the report discloses its evidence tier
352
+ and can withhold the grade.
354
353
 
355
354
  So: **first-class today is Python** (and TypeScript for semantics); every other
356
355
  parsed language is bounded-but-real; anything outside the table is only as
357
- covered as the analyzer you point at it, and with none it is under-reported by
358
- design. Per-language accuracy and limits:
359
- [docs/language-support.md](docs/language-support.md); the adapter catalog:
360
- [docs/adapters.md](docs/adapters.md).
356
+ covered as the analyzer you point at it. Per-language accuracy and limits:
357
+ [docs/language-support.md](docs/language-support.md). Which analyzer covers
358
+ which language, how the opt-in pool extends coverage past the built-in set, and
359
+ why COBOL's external tier is empty:
360
+ [docs/adapters.md](docs/adapters.md#which-analyzer-covers-which-language).
361
361
 
362
362
  ## What it produces
363
363
 
@@ -430,35 +430,29 @@ and costs roughly a grade — use `fetch-depth: 0`.)
430
430
 
431
431
  ## Platform support
432
432
 
433
- **POSIX. Linux and macOS are what this has run on; Windows is untested and not
434
- claimed.** That is a statement about evidence, not a refusal: CI runs Linux,
435
- development runs macOS, and the test suite creates symlinks with no platform
436
- guard (needing Developer Mode on Windows), so it never reaches the point of
437
- telling anyone whether the product works there. Adding `windows-latest` to CI
438
- and fixing what falls out is how that changes.
433
+ **POSIX. Windows is not claimed, for a measured reason.** A `windows-latest` probe
434
+ found most failures in two POSIX-only calls, `os.fchmod` and `os.O_DIRECTORY`. The
435
+ bounded, symlink-refusing write works through a *file descriptor*, so a validated
436
+ path cannot be swapped for a symlink before the write lands — Windows has no
437
+ equivalent and the portable rewrite is the hole. **An unsupported platform never
438
+ buys green by weakening the supported ones.**
439
439
 
440
440
  ## Invokable skill / slash command
441
441
 
442
442
  This repo ships a portable skill under
443
- [`skills/maintainability-agent/`](skills/maintainability-agent/) so
444
- `/maintainability-agent` is one keystroke away. Keep the installed copy in sync
445
- — a drifted skill teaches agents a dead workflow:
443
+ [`skills/maintainability-agent/`](skills/maintainability-agent/), so
444
+ `/maintainability-agent` is one keystroke away in Claude Code, Codex and
445
+ Copilot Chat:
446
446
 
447
447
  ```bash
448
448
  maintainability-agent --install-skill # writes ~/.claude/skills
449
449
  ```
450
450
 
451
- Re-run after every upgrade; a differing installed copy is refused with the
452
- list of differences (`--force-skill` to overwrite).
453
-
454
- | Host | Install destination | Invocation |
455
- |---|---|---|
456
- | Codex / OpenAI | via `skills/maintainability-agent/agents/openai.yaml` | per Codex's skills convention |
457
- | Claude Code | `skills/maintainability-agent/` → `~/.claude/skills/maintainability-agent/` (or repo `.claude/skills/`) | `/maintainability-agent` |
458
- | GitHub Copilot (VS Code) | `skills/maintainability-agent/copilot/maintainability-agent.prompt.md` → `<repo>/.github/prompts/` | `/maintainability-agent` in Copilot Chat |
459
-
460
- For always-on guidance instead of an invokable skill, use
461
- `--init-agent-standards` (see [docs/ide-agent-integration.md](docs/ide-agent-integration.md)).
451
+ Re-run it after every upgrade — a drifted skill teaches agents a dead workflow,
452
+ so a differing installed copy is refused with the list of differences. Per-host
453
+ install destinations, and `--init-agent-standards` for always-on guidance
454
+ instead of an invokable skill:
455
+ [docs/ide-agent-integration.md](docs/ide-agent-integration.md#invokable-skill--slash-command).
462
456
 
463
457
  ## GitHub Action
464
458
 
@@ -1,11 +1,11 @@
1
1
  # Maintainability Agent
2
2
 
3
- ![maintainability-agent — a deterministic audit whose output is a bounded work order for your AI coding agent: fix exactly these findings, refactor nothing else](docs/cover.png)
3
+ ![maintainability-agent — a deterministic audit whose output is a bounded work order for your AI coding agent: fix exactly these findings, refactor nothing else](https://raw.githubusercontent.com/marshallguillory86/maintainability-agent/main/docs/cover.png)
4
4
 
5
5
  **A deterministic, offline maintainability audit whose output is a _bounded
6
6
  work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
7
7
  says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
8
- Version **2.8.0**.
8
+ Version **2.9.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
@@ -162,6 +162,8 @@ integration](docs/ide-agent-integration.md).
162
162
 
163
163
  ## Automation / CI: CLI
164
164
 
165
+ ![The loop, end to end: staged content meets a fast gate that blocks or passes it, the full audit measures what landed, the work order names exactly what to fix, and the repair is checked back against it](https://raw.githubusercontent.com/marshallguillory86/maintainability-agent/main/docs/MA_workflow.jpg)
166
+
165
167
  Use the CLI for scripts, repeatable automation, and CI gates. Copy the example
166
168
  config to your repo root as `maintainability-agent.json`, then:
167
169
 
@@ -221,6 +223,30 @@ maintainability-agent \
221
223
  The [roadmap](docs/roadmap.md#the-remediation-hole-closed-in-210-through-230)
222
224
  has the history of why this was the hole under the product's central claim.
223
225
 
226
+ ### Before the commit
227
+
228
+ The gate above runs after the work is done. `--staged` runs while it is still in
229
+ the author's hands:
230
+
231
+ ```bash
232
+ maintainability-agent --install-precommit-hook # writes .git/hooks/pre-commit
233
+ ```
234
+
235
+ It scans **the index, not the working tree** — stage half a file with `git add
236
+ -p` and keep typing, and what gets measured is what the commit will actually
237
+ contain. Clean, it prints nothing and exits 0. Breaching, it names each path,
238
+ line and remedy and exits 1; `--format json` gives an agent the same findings.
239
+
240
+ It is not a small audit. It produces **no score**, because a diff has no
241
+ population to draw a rate from — only threshold breaches, including a `# noqa`
242
+ this change added. It applies no repository gates: a missing README is a
243
+ property of the repository, not of your diff. And it runs nothing and writes
244
+ nothing, so it costs 0.17s here against the full audit's 266 — a hook slower
245
+ than the author's patience is one they uninstall.
246
+
247
+ `--install-precommit-hook` refuses to replace a hook it did not write, printing
248
+ the line to add to yours instead, and honours `core.hooksPath`.
249
+
224
250
  ## What it analyzes
225
251
 
226
252
  The deterministic scanner reads code from your repo (no LLM calls) and produces
@@ -283,42 +309,16 @@ Any language **not** in that table — Go, Rust, Ruby, PHP, Kotlin, and the rest
283
309
  is **not parsed for declarations by the built-in scanner.** Its files still count
284
310
  toward repo size, but the built-ins produce no function-size, complexity,
285
311
  duplication or dead-code findings for them, and the estimate leans on whatever
286
- evidence *is* available (which is why the report discloses its evidence tier and
287
- can withhold the grade).
288
-
289
- **Which analyzer covers which language** (the opt-in pool, when installed):
290
-
291
- | Language | Built-in scanner | External analyzer |
292
- |---|---|---|
293
- | Python | `ast`, exact | ruff, radon, mypy, vulture, complexipy, interrogate, pydocstyle, pylint, cohesion |
294
- | Java | dedicated scanner | lizard, PMD, Checkstyle, SpotBugs |
295
- | C / C++ / C# | dedicated scanners | lizard, multimetric |
296
- | **Fortran** (free- and fixed-form) | dedicated scanner | **fortitude** — 100+ rules; **lizard** — complexity, NLOC, params |
297
- | **COBOL** | dedicated scanner | **none** — no offline analyzer in the catalog reads it |
298
- | JS / TS / JSX / TSX | brace scanner | ESLint, lizard, jscpd |
299
-
300
- Fortran reached parity in 1.6.0: lizard had read it for years behind a
301
- stale catalog row, so it came out `not-applicable` and never ran. A lint fails
302
- the build if a parsed language has no analyzer measuring complexity — **COBOL is
303
- the one disclosed exemption**, because the tooling that reads it is licensed and
304
- host-resident, so its external tier is empty and the report says so.
305
-
306
- **External analyzer adapters (opt-in pool)** — this is how coverage extends
307
- beyond the built-in set. When you enable the analyzer pool, the tool shells out
308
- to mature analyzers and folds their output in through per-tool
309
- [adapters](docs/adapters.md): **lizard** (cyclomatic complexity across ~a dozen
310
- languages), **jscpd** (cross-language duplication), **ESLint** (JS/TS),
311
- **PMD** / **SpotBugs** (JVM), and others in the catalog. These run only when
312
- selected *and* installed (acquisition is opt-in and off by default), and where
313
- they measured a full concept set they become the *primary* evidence, with the
314
- built-ins as the fallback.
312
+ evidence *is* available — which is why the report discloses its evidence tier
313
+ and can withhold the grade.
315
314
 
316
315
  So: **first-class today is Python** (and TypeScript for semantics); every other
317
316
  parsed language is bounded-but-real; anything outside the table is only as
318
- covered as the analyzer you point at it, and with none it is under-reported by
319
- design. Per-language accuracy and limits:
320
- [docs/language-support.md](docs/language-support.md); the adapter catalog:
321
- [docs/adapters.md](docs/adapters.md).
317
+ covered as the analyzer you point at it. Per-language accuracy and limits:
318
+ [docs/language-support.md](docs/language-support.md). Which analyzer covers
319
+ which language, how the opt-in pool extends coverage past the built-in set, and
320
+ why COBOL's external tier is empty:
321
+ [docs/adapters.md](docs/adapters.md#which-analyzer-covers-which-language).
322
322
 
323
323
  ## What it produces
324
324
 
@@ -391,35 +391,29 @@ and costs roughly a grade — use `fetch-depth: 0`.)
391
391
 
392
392
  ## Platform support
393
393
 
394
- **POSIX. Linux and macOS are what this has run on; Windows is untested and not
395
- claimed.** That is a statement about evidence, not a refusal: CI runs Linux,
396
- development runs macOS, and the test suite creates symlinks with no platform
397
- guard (needing Developer Mode on Windows), so it never reaches the point of
398
- telling anyone whether the product works there. Adding `windows-latest` to CI
399
- and fixing what falls out is how that changes.
394
+ **POSIX. Windows is not claimed, for a measured reason.** A `windows-latest` probe
395
+ found most failures in two POSIX-only calls, `os.fchmod` and `os.O_DIRECTORY`. The
396
+ bounded, symlink-refusing write works through a *file descriptor*, so a validated
397
+ path cannot be swapped for a symlink before the write lands — Windows has no
398
+ equivalent and the portable rewrite is the hole. **An unsupported platform never
399
+ buys green by weakening the supported ones.**
400
400
 
401
401
  ## Invokable skill / slash command
402
402
 
403
403
  This repo ships a portable skill under
404
- [`skills/maintainability-agent/`](skills/maintainability-agent/) so
405
- `/maintainability-agent` is one keystroke away. Keep the installed copy in sync
406
- — a drifted skill teaches agents a dead workflow:
404
+ [`skills/maintainability-agent/`](skills/maintainability-agent/), so
405
+ `/maintainability-agent` is one keystroke away in Claude Code, Codex and
406
+ Copilot Chat:
407
407
 
408
408
  ```bash
409
409
  maintainability-agent --install-skill # writes ~/.claude/skills
410
410
  ```
411
411
 
412
- Re-run after every upgrade; a differing installed copy is refused with the
413
- list of differences (`--force-skill` to overwrite).
414
-
415
- | Host | Install destination | Invocation |
416
- |---|---|---|
417
- | Codex / OpenAI | via `skills/maintainability-agent/agents/openai.yaml` | per Codex's skills convention |
418
- | Claude Code | `skills/maintainability-agent/` → `~/.claude/skills/maintainability-agent/` (or repo `.claude/skills/`) | `/maintainability-agent` |
419
- | GitHub Copilot (VS Code) | `skills/maintainability-agent/copilot/maintainability-agent.prompt.md` → `<repo>/.github/prompts/` | `/maintainability-agent` in Copilot Chat |
420
-
421
- For always-on guidance instead of an invokable skill, use
422
- `--init-agent-standards` (see [docs/ide-agent-integration.md](docs/ide-agent-integration.md)).
412
+ Re-run it after every upgrade — a drifted skill teaches agents a dead workflow,
413
+ so a differing installed copy is refused with the list of differences. Per-host
414
+ install destinations, and `--init-agent-standards` for always-on guidance
415
+ instead of an invokable skill:
416
+ [docs/ide-agent-integration.md](docs/ide-agent-integration.md#invokable-skill--slash-command).
423
417
 
424
418
  ## GitHub Action
425
419
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "maintainability-agent"
7
- version = "2.8.0"
7
+ version = "2.9.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.8.0
3
+ Version: 2.9.0
4
4
  Summary: Maintainability gate and AI remediation prompt generator for CI.
5
5
  Author: Marshall Guillory
6
6
  License: MIT
@@ -39,12 +39,12 @@ Dynamic: license-file
39
39
 
40
40
  # Maintainability Agent
41
41
 
42
- ![maintainability-agent — a deterministic audit whose output is a bounded work order for your AI coding agent: fix exactly these findings, refactor nothing else](docs/cover.png)
42
+ ![maintainability-agent — a deterministic audit whose output is a bounded work order for your AI coding agent: fix exactly these findings, refactor nothing else](https://raw.githubusercontent.com/marshallguillory86/maintainability-agent/main/docs/cover.png)
43
43
 
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.8.0**.
47
+ Version **2.9.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
@@ -201,6 +201,8 @@ integration](docs/ide-agent-integration.md).
201
201
 
202
202
  ## Automation / CI: CLI
203
203
 
204
+ ![The loop, end to end: staged content meets a fast gate that blocks or passes it, the full audit measures what landed, the work order names exactly what to fix, and the repair is checked back against it](https://raw.githubusercontent.com/marshallguillory86/maintainability-agent/main/docs/MA_workflow.jpg)
205
+
204
206
  Use the CLI for scripts, repeatable automation, and CI gates. Copy the example
205
207
  config to your repo root as `maintainability-agent.json`, then:
206
208
 
@@ -260,6 +262,30 @@ maintainability-agent \
260
262
  The [roadmap](docs/roadmap.md#the-remediation-hole-closed-in-210-through-230)
261
263
  has the history of why this was the hole under the product's central claim.
262
264
 
265
+ ### Before the commit
266
+
267
+ The gate above runs after the work is done. `--staged` runs while it is still in
268
+ the author's hands:
269
+
270
+ ```bash
271
+ maintainability-agent --install-precommit-hook # writes .git/hooks/pre-commit
272
+ ```
273
+
274
+ It scans **the index, not the working tree** — stage half a file with `git add
275
+ -p` and keep typing, and what gets measured is what the commit will actually
276
+ contain. Clean, it prints nothing and exits 0. Breaching, it names each path,
277
+ line and remedy and exits 1; `--format json` gives an agent the same findings.
278
+
279
+ It is not a small audit. It produces **no score**, because a diff has no
280
+ population to draw a rate from — only threshold breaches, including a `# noqa`
281
+ this change added. It applies no repository gates: a missing README is a
282
+ property of the repository, not of your diff. And it runs nothing and writes
283
+ nothing, so it costs 0.17s here against the full audit's 266 — a hook slower
284
+ than the author's patience is one they uninstall.
285
+
286
+ `--install-precommit-hook` refuses to replace a hook it did not write, printing
287
+ the line to add to yours instead, and honours `core.hooksPath`.
288
+
263
289
  ## What it analyzes
264
290
 
265
291
  The deterministic scanner reads code from your repo (no LLM calls) and produces
@@ -322,42 +348,16 @@ Any language **not** in that table — Go, Rust, Ruby, PHP, Kotlin, and the rest
322
348
  is **not parsed for declarations by the built-in scanner.** Its files still count
323
349
  toward repo size, but the built-ins produce no function-size, complexity,
324
350
  duplication or dead-code findings for them, and the estimate leans on whatever
325
- evidence *is* available (which is why the report discloses its evidence tier and
326
- can withhold the grade).
327
-
328
- **Which analyzer covers which language** (the opt-in pool, when installed):
329
-
330
- | Language | Built-in scanner | External analyzer |
331
- |---|---|---|
332
- | Python | `ast`, exact | ruff, radon, mypy, vulture, complexipy, interrogate, pydocstyle, pylint, cohesion |
333
- | Java | dedicated scanner | lizard, PMD, Checkstyle, SpotBugs |
334
- | C / C++ / C# | dedicated scanners | lizard, multimetric |
335
- | **Fortran** (free- and fixed-form) | dedicated scanner | **fortitude** — 100+ rules; **lizard** — complexity, NLOC, params |
336
- | **COBOL** | dedicated scanner | **none** — no offline analyzer in the catalog reads it |
337
- | JS / TS / JSX / TSX | brace scanner | ESLint, lizard, jscpd |
338
-
339
- Fortran reached parity in 1.6.0: lizard had read it for years behind a
340
- stale catalog row, so it came out `not-applicable` and never ran. A lint fails
341
- the build if a parsed language has no analyzer measuring complexity — **COBOL is
342
- the one disclosed exemption**, because the tooling that reads it is licensed and
343
- host-resident, so its external tier is empty and the report says so.
344
-
345
- **External analyzer adapters (opt-in pool)** — this is how coverage extends
346
- beyond the built-in set. When you enable the analyzer pool, the tool shells out
347
- to mature analyzers and folds their output in through per-tool
348
- [adapters](docs/adapters.md): **lizard** (cyclomatic complexity across ~a dozen
349
- languages), **jscpd** (cross-language duplication), **ESLint** (JS/TS),
350
- **PMD** / **SpotBugs** (JVM), and others in the catalog. These run only when
351
- selected *and* installed (acquisition is opt-in and off by default), and where
352
- they measured a full concept set they become the *primary* evidence, with the
353
- built-ins as the fallback.
351
+ evidence *is* available — which is why the report discloses its evidence tier
352
+ and can withhold the grade.
354
353
 
355
354
  So: **first-class today is Python** (and TypeScript for semantics); every other
356
355
  parsed language is bounded-but-real; anything outside the table is only as
357
- covered as the analyzer you point at it, and with none it is under-reported by
358
- design. Per-language accuracy and limits:
359
- [docs/language-support.md](docs/language-support.md); the adapter catalog:
360
- [docs/adapters.md](docs/adapters.md).
356
+ covered as the analyzer you point at it. Per-language accuracy and limits:
357
+ [docs/language-support.md](docs/language-support.md). Which analyzer covers
358
+ which language, how the opt-in pool extends coverage past the built-in set, and
359
+ why COBOL's external tier is empty:
360
+ [docs/adapters.md](docs/adapters.md#which-analyzer-covers-which-language).
361
361
 
362
362
  ## What it produces
363
363
 
@@ -430,35 +430,29 @@ and costs roughly a grade — use `fetch-depth: 0`.)
430
430
 
431
431
  ## Platform support
432
432
 
433
- **POSIX. Linux and macOS are what this has run on; Windows is untested and not
434
- claimed.** That is a statement about evidence, not a refusal: CI runs Linux,
435
- development runs macOS, and the test suite creates symlinks with no platform
436
- guard (needing Developer Mode on Windows), so it never reaches the point of
437
- telling anyone whether the product works there. Adding `windows-latest` to CI
438
- and fixing what falls out is how that changes.
433
+ **POSIX. Windows is not claimed, for a measured reason.** A `windows-latest` probe
434
+ found most failures in two POSIX-only calls, `os.fchmod` and `os.O_DIRECTORY`. The
435
+ bounded, symlink-refusing write works through a *file descriptor*, so a validated
436
+ path cannot be swapped for a symlink before the write lands — Windows has no
437
+ equivalent and the portable rewrite is the hole. **An unsupported platform never
438
+ buys green by weakening the supported ones.**
439
439
 
440
440
  ## Invokable skill / slash command
441
441
 
442
442
  This repo ships a portable skill under
443
- [`skills/maintainability-agent/`](skills/maintainability-agent/) so
444
- `/maintainability-agent` is one keystroke away. Keep the installed copy in sync
445
- — a drifted skill teaches agents a dead workflow:
443
+ [`skills/maintainability-agent/`](skills/maintainability-agent/), so
444
+ `/maintainability-agent` is one keystroke away in Claude Code, Codex and
445
+ Copilot Chat:
446
446
 
447
447
  ```bash
448
448
  maintainability-agent --install-skill # writes ~/.claude/skills
449
449
  ```
450
450
 
451
- Re-run after every upgrade; a differing installed copy is refused with the
452
- list of differences (`--force-skill` to overwrite).
453
-
454
- | Host | Install destination | Invocation |
455
- |---|---|---|
456
- | Codex / OpenAI | via `skills/maintainability-agent/agents/openai.yaml` | per Codex's skills convention |
457
- | Claude Code | `skills/maintainability-agent/` → `~/.claude/skills/maintainability-agent/` (or repo `.claude/skills/`) | `/maintainability-agent` |
458
- | GitHub Copilot (VS Code) | `skills/maintainability-agent/copilot/maintainability-agent.prompt.md` → `<repo>/.github/prompts/` | `/maintainability-agent` in Copilot Chat |
459
-
460
- For always-on guidance instead of an invokable skill, use
461
- `--init-agent-standards` (see [docs/ide-agent-integration.md](docs/ide-agent-integration.md)).
451
+ Re-run it after every upgrade — a drifted skill teaches agents a dead workflow,
452
+ so a differing installed copy is refused with the list of differences. Per-host
453
+ install destinations, and `--init-agent-standards` for always-on guidance
454
+ instead of an invokable skill:
455
+ [docs/ide-agent-integration.md](docs/ide-agent-integration.md#invokable-skill--slash-command).
462
456
 
463
457
  ## GitHub Action
464
458
 
@@ -58,6 +58,9 @@ src/maintainability_audit/_metric_adapters.py
58
58
  src/maintainability_audit/_metrics_types.py
59
59
  src/maintainability_audit/_pillars.py
60
60
  src/maintainability_audit/_practice.py
61
+ src/maintainability_audit/_precommit.py
62
+ src/maintainability_audit/_precommit_install.py
63
+ src/maintainability_audit/_precommit_view.py
61
64
  src/maintainability_audit/_pressures.py
62
65
  src/maintainability_audit/_ranges_c.py
63
66
  src/maintainability_audit/_ranges_cobol.py
@@ -144,6 +147,7 @@ tests/test_analyzer_provenance_exclusions.py
144
147
  tests/test_analyzer_scoring_path.py
145
148
  tests/test_analyzer_spread_range.py
146
149
  tests/test_analyzer_xml_bounds.py
150
+ tests/test_anchor_and_mode.py
147
151
  tests/test_anchor_disclosure.py
148
152
  tests/test_annotations_match_behaviour.py
149
153
  tests/test_anticipated_refusals.py
@@ -255,6 +259,7 @@ tests/test_migration_1_0.py
255
259
  tests/test_near_duplicates.py
256
260
  tests/test_network_disclosure.py
257
261
  tests/test_one_setup_across_surfaces_class.py
262
+ tests/test_operator_named_paths.py
258
263
  tests/test_pattern_budget.py
259
264
  tests/test_phase6_claims.py
260
265
  tests/test_pillars_and_practice.py
@@ -264,6 +269,7 @@ tests/test_pmd_audit_closures.py
264
269
  tests/test_pool_runs_by_config.py
265
270
  tests/test_population_floors.py
266
271
  tests/test_practice_nested_gates_class.py
272
+ tests/test_precommit_staged.py
267
273
  tests/test_presentation_refusals.py
268
274
  tests/test_promises.py
269
275
  tests/test_prompt_escalation_once.py
@@ -1,3 +1,3 @@
1
1
  """Maintainability Audit CI."""
2
2
 
3
- __version__ = "2.8.0"
3
+ __version__ = "2.9.0"
@@ -23,8 +23,23 @@ def _add_gate_arguments(parser: argparse.ArgumentParser) -> None:
23
23
  Split from `add_arguments` when the conformance flags took it past this
24
24
  project's own function-length gate — which is the gate doing its job on
25
25
  the change that added a gate.
26
+
27
+ `--staged` belongs here rather than with the artifact flags: it
28
+ produces no artifact and decides whether work proceeds, which is what
29
+ every other flag in this group does. It differs only in *when* — a
30
+ commit rather than a build — and that is the point of it.
26
31
  """
27
32
  parser.add_argument("--fail-on-gate", action="store_true", help="Exit 1 when hard gates fail.")
33
+ parser.add_argument(
34
+ "--staged", action="store_true",
35
+ help="Scan what the git index will commit, for a pre-commit hook. "
36
+ "Reads the index rather than the working tree, so half-staged "
37
+ "edits are measured as they will land. Reports threshold "
38
+ "breaches only and never a score — a diff has no population to "
39
+ "draw a rate from. Applies no repository gates, writes nothing, "
40
+ "and never runs the opt-in test suite. Exits 1 when something "
41
+ "blocks, silently 0 when nothing does.",
42
+ )
28
43
  parser.add_argument(
29
44
  "--conformance",
30
45
  metavar="REVSPEC",
@@ -158,6 +173,14 @@ def _add_setup_actions(parser: argparse.ArgumentParser) -> None:
158
173
  "exit. Re-run after every upgrade: an installed skill that "
159
174
  "drifts from the shipped one teaches agents a dead workflow.",
160
175
  )
176
+ parser.add_argument(
177
+ "--install-precommit-hook", action="store_true",
178
+ help="Write a git pre-commit hook that runs --staged, and exit. "
179
+ "Refuses to replace a hook this tool did not write, and "
180
+ "prints the line to add to that hook instead; replaces its "
181
+ "own without asking, because that is an upgrade. Honours "
182
+ "core.hooksPath.",
183
+ )
161
184
  parser.add_argument(
162
185
  "--skills-dir", default=None, metavar="DIR",
163
186
  help="Where --install-skill writes (default: ~/.claude/skills).",
@@ -98,6 +98,49 @@ _SUPPRESSION = tuple(
98
98
  )
99
99
 
100
100
 
101
+ def _is_quoted(text: str, start: int) -> bool:
102
+ """Whether the marker at `start` is being *mentioned* rather than used.
103
+
104
+ A directive is never immediately preceded by a quote or a backtick,
105
+ and never sits inside an open backtick span. Prose about directives
106
+ does both constantly — this file's own comments do it, and so does
107
+ the docstring that says a marker in a docstring is not a suppression.
108
+ """
109
+ prefix = text[:start]
110
+ if prefix[-1:] in {"`", "'", '"'}:
111
+ return True
112
+ return prefix.count("`") % 2 == 1
113
+
114
+
115
+ def markers_in(text: str) -> str | None:
116
+ """The suppression this line declares, or None if it only mentions one.
117
+
118
+ The one place that question is answered, because it is asked twice —
119
+ by the conformance record after a change, and by the pre-commit scan
120
+ before one — and a rule that disagrees with itself between the hook
121
+ and the gate blocks commits CI would pass.
122
+
123
+ D108: the marker set's own comment promised that "`# type: ignore` in
124
+ a docstring explaining the convention is not a suppression", and for
125
+ four versions nothing enforced it. The `--staged` scan found it
126
+ immediately, by blocking the commit of a docstring containing the
127
+ words `# noqa` — the first time the rule ran somewhere a false
128
+ positive cost something.
129
+
130
+ Still narrow, and deliberately: a marker written into ordinary prose
131
+ with no quoting around it is still reported. Quoting is the signal
132
+ that is actually reliable, and widening past it starts guessing at
133
+ English. This paragraph cannot give the example it would like to,
134
+ because writing one unquoted would report this line — which is the
135
+ rule demonstrating itself, and the reason the sentence stays abstract.
136
+ """
137
+ for pattern, label in _SUPPRESSION:
138
+ match = pattern.search(text)
139
+ if match and not _is_quoted(text, match.start()):
140
+ return label
141
+ return None
142
+
143
+
101
144
  def suppressions_added(
102
145
  added: dict[str, list[tuple[int, str]]], named: set[str]
103
146
  ) -> list[dict[str, Any]]:
@@ -120,15 +163,14 @@ def suppressions_added(
120
163
  found: list[dict[str, Any]] = []
121
164
  for path in sorted(added):
122
165
  for line_number, text in added[path]:
123
- for pattern, label in _SUPPRESSION:
124
- if pattern.search(text):
125
- found.append({
126
- "path": path,
127
- "line": line_number,
128
- "marker": label,
129
- "on_named_path": path in named,
130
- })
131
- break
166
+ label = markers_in(text)
167
+ if label:
168
+ found.append({
169
+ "path": path,
170
+ "line": line_number,
171
+ "marker": label,
172
+ "on_named_path": path in named,
173
+ })
132
174
  return found
133
175
 
134
176