maintainability-agent 3.0.0__tar.gz → 3.0.2__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 (358) hide show
  1. {maintainability_agent-3.0.0/src/maintainability_agent.egg-info → maintainability_agent-3.0.2}/PKG-INFO +2 -2
  2. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/README.md +1 -1
  3. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/pyproject.toml +1 -1
  4. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2/src/maintainability_agent.egg-info}/PKG-INFO +2 -2
  5. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_agent.egg-info/SOURCES.txt +2 -0
  6. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/__init__.py +1 -1
  7. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_in_loop.py +14 -1
  8. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_operator_reads.py +60 -9
  9. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_runner.py +31 -4
  10. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_second_source.py +36 -4
  11. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/config.py +1 -1
  12. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/metrics.py +25 -7
  13. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/source.py +8 -4
  14. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_criteria_completion.py +31 -1
  15. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_in_loop_check.py +38 -0
  16. maintainability_agent-3.0.2/tests/test_language_support_honesty.py +112 -0
  17. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_operator_named_reads.py +52 -3
  18. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_pool_runs_by_config.py +5 -1
  19. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_source_index.py +17 -8
  20. maintainability_agent-3.0.2/tests/test_tool_resolution.py +166 -0
  21. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/LICENSE +0 -0
  22. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/setup.cfg +0 -0
  23. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_agent.egg-info/dependency_links.txt +0 -0
  24. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_agent.egg-info/entry_points.txt +0 -0
  25. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_agent.egg-info/requires.txt +0 -0
  26. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_agent.egg-info/top_level.txt +0 -0
  27. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/__main__.py +0 -0
  28. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_adapters.py +0 -0
  29. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_analysis.py +0 -0
  30. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_analyzer_sections.py +0 -0
  31. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_anchor.py +0 -0
  32. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_arguments.py +0 -0
  33. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_aspects.py +0 -0
  34. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_assets/analyzer-catalog.json +0 -0
  35. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_assets/standard.md +0 -0
  36. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_attestation.py +0 -0
  37. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_backfill.py +0 -0
  38. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_bands.py +0 -0
  39. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_banner.py +0 -0
  40. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_built_ins.py +0 -0
  41. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_calibration.py +0 -0
  42. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_catalog.py +0 -0
  43. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_charts.py +0 -0
  44. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_cognitive.py +0 -0
  45. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_config_defaults.py +0 -0
  46. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_conformance.py +0 -0
  47. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_corroborate.py +0 -0
  48. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_coverage_notes.py +0 -0
  49. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_criteria_scope.py +0 -0
  50. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_derive.py +0 -0
  51. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_discovery.py +0 -0
  52. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_documents.py +0 -0
  53. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_economics.py +0 -0
  54. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_economics_view.py +0 -0
  55. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_environment.py +0 -0
  56. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_evidence_view.py +0 -0
  57. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_finding_match.py +0 -0
  58. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_first_run.py +0 -0
  59. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_formula.py +0 -0
  60. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_gates.py +0 -0
  61. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_generic.py +0 -0
  62. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_grant_ledger.py +0 -0
  63. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_history_view.py +0 -0
  64. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_hostile_prompt.py +0 -0
  65. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_hotspots.py +0 -0
  66. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_html_report_sections.py +0 -0
  67. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_html_view.py +0 -0
  68. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_identity.py +0 -0
  69. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_in_loop_view.py +0 -0
  70. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_jvm_adapters.py +0 -0
  71. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_markdown_sections.py +0 -0
  72. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_masking.py +0 -0
  73. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_mcp_audit.py +0 -0
  74. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_mcp_gate.py +0 -0
  75. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_mcp_grants.py +0 -0
  76. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_mcp_refusals.py +0 -0
  77. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_mcp_resources.py +0 -0
  78. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_mcp_setup.py +0 -0
  79. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_metric_adapters.py +0 -0
  80. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_metrics_types.py +0 -0
  81. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_pillars.py +0 -0
  82. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_practice.py +0 -0
  83. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_precommit.py +0 -0
  84. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_precommit_install.py +0 -0
  85. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_precommit_view.py +0 -0
  86. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_pressures.py +0 -0
  87. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_ranges_c.py +0 -0
  88. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_ranges_cobol.py +0 -0
  89. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_ranges_core.py +0 -0
  90. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_ranges_cpp.py +0 -0
  91. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_ranges_csharp.py +0 -0
  92. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_ranges_fortran.py +0 -0
  93. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_ranges_go.py +0 -0
  94. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_ranges_java.py +0 -0
  95. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_ranges_js.py +0 -0
  96. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_ranges_php.py +0 -0
  97. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_ranges_ruby.py +0 -0
  98. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_ranges_rust.py +0 -0
  99. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_ranges_swift.py +0 -0
  100. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_ratchet.py +0 -0
  101. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_recurrence.py +0 -0
  102. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_run_comparison.py +0 -0
  103. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_safe_write.py +0 -0
  104. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_scan_history.py +0 -0
  105. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_scan_view.py +0 -0
  106. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_selection.py +0 -0
  107. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_semantic.py +0 -0
  108. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_semantic_policy.py +0 -0
  109. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_semantic_ts.py +0 -0
  110. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_semantic_view.py +0 -0
  111. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_setup_errors.py +0 -0
  112. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_setup_persist.py +0 -0
  113. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_skill_data/SKILL.md +0 -0
  114. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_skill_data/agents/anthropic.yaml +0 -0
  115. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_skill_data/agents/copilot.yaml +0 -0
  116. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_skill_data/agents/openai.yaml +0 -0
  117. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_skill_data/copilot/maintainability-agent.prompt.md +0 -0
  118. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_skill_data/references/finding-taxonomy.md +0 -0
  119. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_skill_install.py +0 -0
  120. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_stored_grants.py +0 -0
  121. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_tdd_view.py +0 -0
  122. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_test_commands.py +0 -0
  123. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_test_execution.py +0 -0
  124. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_test_pairing.py +0 -0
  125. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_tokens.py +0 -0
  126. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_tool_adapters.py +0 -0
  127. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_trends.py +0 -0
  128. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_user_config.py +0 -0
  129. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_verdict_adapters.py +0 -0
  130. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_verification.py +0 -0
  131. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_work_order.py +0 -0
  132. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_work_order_view.py +0 -0
  133. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_work_order_weights.py +0 -0
  134. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/_xml.py +0 -0
  135. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/baseline.py +0 -0
  136. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/cli.py +0 -0
  137. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/deadcode.py +0 -0
  138. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/declarations.py +0 -0
  139. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/duplication.py +0 -0
  140. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/evidence.py +0 -0
  141. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/git_tools.py +0 -0
  142. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/history.py +0 -0
  143. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/idioms.py +0 -0
  144. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/instructions.py +0 -0
  145. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/mcp_server.py +0 -0
  146. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/prompts.py +0 -0
  147. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/renderers.py +0 -0
  148. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/report.py +0 -0
  149. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/sarif.py +0 -0
  150. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/scoring.py +0 -0
  151. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/src/maintainability_audit/similarity.py +0 -0
  152. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_acquisition_trust.py +0 -0
  153. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_adapter_docs.py +0 -0
  154. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_adapter_recordings.py +0 -0
  155. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_adapters.py +0 -0
  156. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_added_lines.py +0 -0
  157. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_analysis_coverage.py +0 -0
  158. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_analysis_execution.py +0 -0
  159. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_analyzer_bridge.py +0 -0
  160. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_analyzer_config_isolation.py +0 -0
  161. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_analyzer_estimate_claims.py +0 -0
  162. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_analyzer_pinning.py +0 -0
  163. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_analyzer_primary_estimate.py +0 -0
  164. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_analyzer_provenance.py +0 -0
  165. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_analyzer_provenance_exclusions.py +0 -0
  166. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_analyzer_scoring_path.py +0 -0
  167. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_analyzer_spread_range.py +0 -0
  168. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_analyzer_xml_bounds.py +0 -0
  169. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_anchor_and_mode.py +0 -0
  170. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_anchor_disclosure.py +0 -0
  171. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_annotations_match_behaviour.py +0 -0
  172. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_anticipated_refusals.py +0 -0
  173. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_architecture.py +0 -0
  174. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_artifact_write_route_class.py +0 -0
  175. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_aspect_coverage.py +0 -0
  176. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_asset_snapshot_class.py +0 -0
  177. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_ast_reading_resolves_spellings.py +0 -0
  178. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_attestation.py +0 -0
  179. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_audit_components.py +0 -0
  180. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_authorization_freshness.py +0 -0
  181. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_authorship_gates.py +0 -0
  182. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_backfill.py +0 -0
  183. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_band_concealment_class.py +0 -0
  184. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_band_concealment_p3.py +0 -0
  185. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_band_pressures.py +0 -0
  186. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_bands.py +0 -0
  187. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_body_or_signature_class.py +0 -0
  188. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_c_declarations.py +0 -0
  189. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_calibration_corpus.py +0 -0
  190. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_calibration_drift.py +0 -0
  191. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_calibration_tooling.py +0 -0
  192. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_catalog_selection.py +0 -0
  193. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_catalog_tier_promise.py +0 -0
  194. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_chat_primary_docs.py +0 -0
  195. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_checkstyle_adapter.py +0 -0
  196. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_checkstyle_catalog.py +0 -0
  197. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_ci_history_recipe.py +0 -0
  198. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_ci_installs_the_analyzer_pool.py +0 -0
  199. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_claimed_languages.py +0 -0
  200. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_cli.py +0 -0
  201. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_clone_group_class.py +0 -0
  202. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_cobol_declarations.py +0 -0
  203. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_cognitive_complexity.py +0 -0
  204. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_cohort_statistics.py +0 -0
  205. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_concealment_properties.py +0 -0
  206. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_config_shape.py +0 -0
  207. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_consent_and_grant.py +0 -0
  208. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_consumer_migration.py +0 -0
  209. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_contract_claims.py +0 -0
  210. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_corroborate.py +0 -0
  211. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_coverage_by_language.py +0 -0
  212. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_cpp_declarations.py +0 -0
  213. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_csharp_declarations.py +0 -0
  214. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_d15_composition.py +0 -0
  215. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_d15_goal_directed.py +0 -0
  216. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_dead_code.py +0 -0
  217. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_declaration_criteria_scope.py +0 -0
  218. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_declaration_edge_cases.py +0 -0
  219. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_declaration_grading.py +0 -0
  220. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_declaration_ranges.py +0 -0
  221. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_declaration_source.py +0 -0
  222. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_declared_authorship.py +0 -0
  223. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_declared_imports.py +0 -0
  224. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_derivation_mix.py +0 -0
  225. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_determinism.py +0 -0
  226. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_dimension_ratchet.py +0 -0
  227. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_direct_runner_availability_class.py +0 -0
  228. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_discovery.py +0 -0
  229. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_doc_claims.py +0 -0
  230. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_docs_links.py +0 -0
  231. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_economic_context.py +0 -0
  232. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_empty_output_not_clean.py +0 -0
  233. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_empty_output_not_clean_adapters.py +0 -0
  234. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_environment_work_order.py +0 -0
  235. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_evidence_invariants.py +0 -0
  236. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_evidence_normalization.py +0 -0
  237. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_evidence_properties.py +0 -0
  238. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_exclusion_dialects.py +0 -0
  239. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_falsifier_standard.py +0 -0
  240. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_file_list_cap_class.py +0 -0
  241. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_finding_identity.py +0 -0
  242. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_first_run_elicitation.py +0 -0
  243. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_first_run_prompt.py +0 -0
  244. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_first_run_reelicitation.py +0 -0
  245. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_fix_breadth_window.py +0 -0
  246. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_fixed_form_fortran.py +0 -0
  247. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_format_ask.py +0 -0
  248. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_fortran_declarations.py +0 -0
  249. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_fortran_metrics.py +0 -0
  250. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_fortran_practice.py +0 -0
  251. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_git_argv.py +0 -0
  252. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_git_read_only.py +0 -0
  253. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_git_readonly_class.py +0 -0
  254. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_go_declarations.py +0 -0
  255. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_grammar_constructs.py +0 -0
  256. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_grammar_token_coverage.py +0 -0
  257. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_grant_only_user_tier.py +0 -0
  258. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_history.py +0 -0
  259. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_history_boundary.py +0 -0
  260. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_history_manifest.py +0 -0
  261. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_history_schema2.py +0 -0
  262. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_history_window.py +0 -0
  263. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_hostile_prompt.py +0 -0
  264. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_html_executive.py +0 -0
  265. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_html_section_parity_class.py +0 -0
  266. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_identity_docs.py +0 -0
  267. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_identity_resolution.py +0 -0
  268. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_idiom_divergence.py +0 -0
  269. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_in_loop_budgets.py +0 -0
  270. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_installed_wheel_runtime.py +0 -0
  271. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_instruction_targets.py +0 -0
  272. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_java_range_guard.py +0 -0
  273. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_java_ranges.py +0 -0
  274. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_java_scored.py +0 -0
  275. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_java_wiring.py +0 -0
  276. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_js_complexity_operators.py +0 -0
  277. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_js_declarations.py +0 -0
  278. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_language_coverage.py +0 -0
  279. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_mcp_baseline_payload.py +0 -0
  280. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_mcp_history.py +0 -0
  281. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_mcp_server.py +0 -0
  282. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_migration_1_0.py +0 -0
  283. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_near_duplicates.py +0 -0
  284. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_network_disclosure.py +0 -0
  285. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_one_setup_across_surfaces_class.py +0 -0
  286. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_operator_named_paths.py +0 -0
  287. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_pattern_budget.py +0 -0
  288. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_phase6_claims.py +0 -0
  289. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_php_declarations.py +0 -0
  290. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_pillars_and_practice.py +0 -0
  291. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_platform_claim.py +0 -0
  292. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_pmd_adapter.py +0 -0
  293. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_pmd_audit_closures.py +0 -0
  294. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_policy_claims.py +0 -0
  295. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_population_floors.py +0 -0
  296. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_practice_nested_gates_class.py +0 -0
  297. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_precommit_staged.py +0 -0
  298. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_presentation_refusals.py +0 -0
  299. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_promises.py +0 -0
  300. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_prompt_escalation_once.py +0 -0
  301. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_prompt_honesty.py +0 -0
  302. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_prompt_location_escalation_class.py +0 -0
  303. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_prove_falsifiers.py +0 -0
  304. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_python_complexity.py +0 -0
  305. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_python_declarations.py +0 -0
  306. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_read_boundary.py +0 -0
  307. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_readme_claims.py +0 -0
  308. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_recurrence.py +0 -0
  309. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_release_plan.py +0 -0
  310. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_release_plan_status.py +0 -0
  311. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_report_remedy_copy.py +0 -0
  312. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_roles_recorded.py +0 -0
  313. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_root_grants.py +0 -0
  314. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_rubric_ignores_project_lint_config.py +0 -0
  315. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_ruby_declarations.py +0 -0
  316. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_run_comparison.py +0 -0
  317. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_runner.py +0 -0
  318. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_rust_declarations.py +0 -0
  319. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_sarif.py +0 -0
  320. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_scan_history.py +0 -0
  321. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_scan_scope.py +0 -0
  322. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_scanner_unsafe_direction.py +0 -0
  323. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_scanning.py +0 -0
  324. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_schema_fidelity.py +0 -0
  325. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_scope_conformance.py +0 -0
  326. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_scoring_calibration.py +0 -0
  327. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_semantic_policy.py +0 -0
  328. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_setup_gate_completeness.py +0 -0
  329. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_setup_gate_independent_contract.py +0 -0
  330. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_setup_precondition.py +0 -0
  331. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_skill_install.py +0 -0
  332. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_sonar_resolve.py +0 -0
  333. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_spotbugs_adapter.py +0 -0
  334. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_spotbugs_catalog.py +0 -0
  335. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_stage8_contract.py +0 -0
  336. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_suite_isolation.py +0 -0
  337. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_swift_declarations.py +0 -0
  338. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_tdd_structure.py +0 -0
  339. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_test_commands.py +0 -0
  340. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_test_execution_class.py +0 -0
  341. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_three_presentations.py +0 -0
  342. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_tree_chosen_spawn.py +0 -0
  343. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_trends.py +0 -0
  344. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_type_fields_not_functions_class.py +0 -0
  345. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_uat_falsifiers.py +0 -0
  346. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_unread_code.py +0 -0
  347. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_user_config_tier.py +0 -0
  348. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_verification_audit_closures.py +0 -0
  349. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_verified_grade.py +0 -0
  350. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_version_claim.py +0 -0
  351. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_wheel_contents.py +0 -0
  352. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_work_order.py +0 -0
  353. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_work_order_items.py +0 -0
  354. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_work_order_selection.py +0 -0
  355. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_workflow_supply_chain.py +0 -0
  356. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_write_boundary.py +0 -0
  357. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_write_route_class.py +0 -0
  358. {maintainability_agent-3.0.0 → maintainability_agent-3.0.2}/tests/test_written_record.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maintainability-agent
3
- Version: 3.0.0
3
+ Version: 3.0.2
4
4
  Summary: Maintainability gate and AI remediation prompt generator for CI.
5
5
  Author: Marshall Guillory
6
6
  License: MIT
@@ -45,7 +45,7 @@ Dynamic: license-file
45
45
  **A deterministic, offline maintainability audit whose output is a _bounded
46
46
  work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
47
47
  says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
48
- Version **3.0.0**.
48
+ Version **3.0.2**.
49
49
 
50
50
  **Planned, not shipped:** the audit tool will become `maintainability-audit`;
51
51
  a new `maintainability-agent` companion will run it and automate human-selected
@@ -5,7 +5,7 @@
5
5
  **A deterministic, offline maintainability audit whose output is a _bounded
6
6
  work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
7
7
  says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
8
- Version **3.0.0**.
8
+ Version **3.0.2**.
9
9
 
10
10
  **Planned, not shipped:** the audit tool will become `maintainability-audit`;
11
11
  a new `maintainability-agent` companion will run it and automate human-selected
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "maintainability-agent"
7
- version = "3.0.0"
7
+ version = "3.0.2"
8
8
  description = "Maintainability gate and AI remediation prompt generator for CI."
9
9
  readme = "README.md"
10
10
  # 3.11, not 3.10. `_discovery`, `_pillars` and `_runner` all import
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maintainability-agent
3
- Version: 3.0.0
3
+ Version: 3.0.2
4
4
  Summary: Maintainability gate and AI remediation prompt generator for CI.
5
5
  Author: Marshall Guillory
6
6
  License: MIT
@@ -45,7 +45,7 @@ Dynamic: license-file
45
45
  **A deterministic, offline maintainability audit whose output is a _bounded
46
46
  work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
47
47
  says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
48
- Version **3.0.0**.
48
+ Version **3.0.2**.
49
49
 
50
50
  **Planned, not shipped:** the audit tool will become `maintainability-audit`;
51
51
  a new `maintainability-agent` companion will run it and automate human-selected
@@ -269,6 +269,7 @@ tests/test_java_wiring.py
269
269
  tests/test_js_complexity_operators.py
270
270
  tests/test_js_declarations.py
271
271
  tests/test_language_coverage.py
272
+ tests/test_language_support_honesty.py
272
273
  tests/test_mcp_baseline_payload.py
273
274
  tests/test_mcp_history.py
274
275
  tests/test_mcp_server.py
@@ -335,6 +336,7 @@ tests/test_tdd_structure.py
335
336
  tests/test_test_commands.py
336
337
  tests/test_test_execution_class.py
337
338
  tests/test_three_presentations.py
339
+ tests/test_tool_resolution.py
338
340
  tests/test_tree_chosen_spawn.py
339
341
  tests/test_trends.py
340
342
  tests/test_type_fields_not_functions_class.py
@@ -1,3 +1,3 @@
1
1
  """Maintainability Audit CI."""
2
2
 
3
- __version__ = "3.0.0"
3
+ __version__ = "3.0.2"
@@ -283,7 +283,20 @@ def _is_unified_diff(text: str) -> bool:
283
283
  has_old = any(line.startswith("--- ") for line in lines)
284
284
  has_new = any(line.startswith("+++ ") for line in lines)
285
285
  has_hunk = any(_HUNK_HEADER.match(line) for line in lines)
286
- return has_hunk and has_old and has_new
286
+ # Either half of the shape is enough, because an agent pastes either
287
+ # half (D138). Requiring all three read a hunk-only fragment — the
288
+ # commonest paste of the two, since a chat window shows the hunk and
289
+ # not the file headers — as file content, and every suffix `--check`
290
+ # claims to parse reported `declarations_read: true` on it. That is
291
+ # absence read as a pass arriving through the same door D133 closed
292
+ # for the complete diff, one fragment shape to the left.
293
+ #
294
+ # The mention-versus-assertion guard is unaffected and is why this
295
+ # can be loosened safely: `_HUNK_HEADER` is anchored with `match`,
296
+ # so a hunk header quoted inside a string — `"paste a hunk like
297
+ # @@ -1,3 +1,4 @@ here"` — is not at the start of its line and does
298
+ # not count. Somebody writing *about* a diff still parses.
299
+ return has_hunk or (has_old and has_new)
287
300
 
288
301
 
289
302
  def _parses(path: str, text: str) -> bool:
@@ -30,6 +30,61 @@ class PathNotAllowed(ValueError):
30
30
  MAX_OPERATOR_FILE_BYTES = 8 * 1024 * 1024
31
31
 
32
32
 
33
+ def open_regular_file(path: Path, why: str) -> int:
34
+ """Open a path only if it is a regular file, and hand back the handle.
35
+
36
+ The one regular-file control, so there is never a second
37
+ implementation of it to drift. `read_operator_file` reads what an
38
+ operator named; `read_source_file` reads what discovery found. Those
39
+ are different populations with different decoding and different size
40
+ rules, and exactly one thing in common — neither may block on a FIFO
41
+ or drain memory from a device.
42
+
43
+ Splitting it out is D141. The operator-named door was hardened after
44
+ S8707 and the scan door was not, so a FIFO named `src/hang.py` still
45
+ stopped an audit dead: `read_text` on it waits for a writer that
46
+ never comes. Listing the state modules was never going to catch that,
47
+ because a discovered source path is a different population from a
48
+ configured one.
49
+
50
+ `O_NONBLOCK` is what makes the check possible at all — without it the
51
+ open itself blocks before any validation can run — and the handle is
52
+ returned rather than the path, so the caller reads *through what was
53
+ checked*. Re-opening by name would reintroduce the
54
+ time-of-check/time-of-use gap this exists to close.
55
+ """
56
+ import stat as stat_module
57
+
58
+ handle = os.open(path, os.O_RDONLY | os.O_NONBLOCK)
59
+ if not stat_module.S_ISREG(os.fstat(handle).st_mode):
60
+ os.close(handle)
61
+ raise PathNotAllowed(f"{path} is not a regular file. {why}")
62
+ return handle
63
+
64
+
65
+ def read_source_file(path: Path) -> list[str]:
66
+ """Read a discovered source file, tolerating undecodable bytes.
67
+
68
+ Same regular-file guard as `read_operator_file`, deliberately
69
+ different in the two places a scan differs from a configured read:
70
+ it does not cap the size, because a large file in the tree is a
71
+ finding rather than a refusal, and it replaces undecodable bytes
72
+ instead of raising, because a scan must not abort on one file's
73
+ encoding.
74
+ """
75
+ handle = open_regular_file(
76
+ path,
77
+ "The scan reads files discovery found; a device, socket or FIFO "
78
+ "in the tree would block the audit rather than be measured.",
79
+ )
80
+ try:
81
+ with os.fdopen(handle, "rb", closefd=False) as opened:
82
+ raw = opened.read()
83
+ finally:
84
+ os.close(handle)
85
+ return raw.decode("utf-8", errors="replace").splitlines()
86
+
87
+
33
88
  def read_operator_file(path: Path) -> str:
34
89
  """Read a file the operator named, after checking it is one.
35
90
 
@@ -67,8 +122,6 @@ def read_operator_file(path: Path) -> str:
67
122
  ordinary setup. The audited tree's own default path is a different
68
123
  question and `discovered_config` already refuses a symlink there.
69
124
  """
70
- import stat as stat_module
71
-
72
125
  # Opened once, then checked and read **through that handle** — the
73
126
  # same discipline `_safe_write` uses for writes, and for the same
74
127
  # reason. Checking `os.stat(path)` and then calling `path.read_text()`
@@ -79,15 +132,13 @@ def read_operator_file(path: Path) -> str:
79
132
  # for reading otherwise blocks until a writer appears, so the process
80
133
  # would hang *before* reaching any validation — the very failure this
81
134
  # function exists to prevent.
82
- handle = os.open(path, os.O_RDONLY | os.O_NONBLOCK)
135
+ handle = open_regular_file(
136
+ path,
137
+ "This reads configuration and baselines; a device, socket or FIFO "
138
+ "named here would block or exhaust memory rather than parse.",
139
+ )
83
140
  try:
84
141
  info = os.fstat(handle)
85
- if not stat_module.S_ISREG(info.st_mode):
86
- raise PathNotAllowed(
87
- f"{path} is not a regular file. This reads configuration "
88
- "and baselines; a device, socket or FIFO named here would "
89
- "block or exhaust memory rather than parse."
90
- )
91
142
  if info.st_size > MAX_OPERATOR_FILE_BYTES:
92
143
  raise PathNotAllowed(
93
144
  f"{path} is {info.st_size} bytes, over the "
@@ -32,8 +32,10 @@ import re
32
32
  import shutil
33
33
  import subprocess
34
34
  import sys
35
+ import sysconfig
35
36
  import tempfile
36
37
  import time
38
+ from contextlib import suppress
37
39
  from dataclasses import dataclass, field
38
40
  from enum import StrEnum
39
41
  from pathlib import Path
@@ -137,13 +139,38 @@ def locate(executable: str) -> str | None:
137
139
  `maintainability-audit --analyzers` finds almost nothing and reports
138
140
  an honest but useless "not installed" for every one of them.
139
141
  """
140
- own_bin = Path(sys.executable).parent
141
- candidate = own_bin / executable
142
- if candidate.is_file():
143
- return str(candidate)
142
+ for directory in _agent_script_dirs():
143
+ candidate = directory / executable
144
+ if candidate.is_file():
145
+ return str(candidate)
144
146
  return shutil.which(executable)
145
147
 
146
148
 
149
+ def _agent_script_dirs() -> tuple[Path, ...]:
150
+ """Where this interpreter's console scripts land, in precedence order.
151
+
152
+ Two directories, because `pip install lizard` writes to a different
153
+ one depending on whether the agent lives in a virtualenv. In a venv
154
+ it is the interpreter's own `bin`. On a **system** Python it is the
155
+ per-user scripts directory, because the framework directory is not
156
+ writable and pip falls back to `--user` — on macOS that is
157
+ `~/Library/Python/3.11/bin`, which is not on the default `PATH`.
158
+
159
+ Searching only the first was D142. A user followed the environment
160
+ work order's own instruction, ran `pip install lizard`, and the next
161
+ audit still reported it "not installed or not on PATH" — because it
162
+ was installed exactly where neither this function nor `PATH` looked.
163
+ The whole Python pool went missing that way on the MCP surface, so
164
+ `declarations` fell to the built-in tier while the report's own
165
+ remedy could not fix it.
166
+ """
167
+ directories = [Path(sys.executable).parent]
168
+ with suppress(Exception):
169
+ scheme = sysconfig.get_preferred_scheme("user")
170
+ directories.append(Path(sysconfig.get_path("scripts", scheme=scheme)))
171
+ return tuple(directories)
172
+
173
+
147
174
  # Variables that make an interpreter or a linter load code from
148
175
  # somewhere the operator did not choose. `PYTHONPATH` and `NODE_PATH`
149
176
  # put a directory on an import path; `PYTHONSTARTUP` names a file
@@ -37,10 +37,42 @@ def analyzer_scored(external: ExternalPressures | None) -> list[str]:
37
37
  """
38
38
  if external is None:
39
39
  return []
40
- return sorted({
41
- name for population in (external.all_code, external.production)
42
- for name, value in population.items() if value is not None
43
- })
40
+ filled = _completed_from_the_built_in_tier(external)
41
+ return sorted(
42
+ f"{name} (completed by built-in detectors)" if name in filled else name
43
+ for name in {
44
+ name for population in (external.all_code, external.production)
45
+ for name, value in population.items() if value is not None
46
+ }
47
+ )
48
+
49
+
50
+ def _completed_from_the_built_in_tier(external: ExternalPressures) -> set[str]:
51
+ """Dimensions whose criterion set the analyzers did not cover alone.
52
+
53
+ A published rate that mixes two counting conventions must say so
54
+ (D140). `declarations` is scored on cyclomatic complexity, declaration
55
+ lines and cognitive complexity, and no analyzer in the pool supplies
56
+ all three for any language but Python — `lizard` reports no cognitive
57
+ complexity at all — so on thirteen of the fourteen parsed languages
58
+ the built-in scanner fills that criterion per declaration.
59
+
60
+ Naming it is ADR 006 working rather than an exception to it: the
61
+ analyzer tier stays primary for what it measures, and the label stops
62
+ crediting `lizard` for the scanner's reading. Derived from the raw
63
+ measurements the pressures were reduced from, so it cannot disagree
64
+ with what was actually published.
65
+ """
66
+ from ._criteria_scope import _languages_missing_a_criterion, _unit_languages
67
+ from ._pressures import _units_by_concept
68
+
69
+ if not external.measurements:
70
+ return set()
71
+ measurements = list(external.measurements)
72
+ per_unit = _units_by_concept(measurements, production_only=False)
73
+ if not _languages_missing_a_criterion(per_unit, _unit_languages(measurements)):
74
+ return set()
75
+ return {"declarations"}
44
76
 
45
77
 
46
78
  def widen_for_disagreement(
@@ -20,7 +20,7 @@ from ._operator_reads import (
20
20
  # still the door every caller comes through.
21
21
  __all__ = ["DEFAULT_CONFIG", "DEFAULT_IDIOM_GROUPS", "PROJECT_URL", "VERSION"]
22
22
 
23
- VERSION = "3.0.0"
23
+ VERSION = "3.0.2"
24
24
 
25
25
  PROJECT_URL = "https://github.com/marshallguillory86/maintainability-agent"
26
26
 
@@ -22,6 +22,7 @@ from ._metrics_types import KNOWN_SOURCE_SUFFIXES, FileMetric, FunctionMetric
22
22
  # Re-exported because `metrics.is_test_path` is the import path a dozen
23
23
  # callers already use, and the move is not their business.
24
24
  from ._metrics_types import is_test_path as is_test_path
25
+ from ._operator_reads import PathNotAllowed, read_source_file
25
26
  from .declarations import DECLARATION_SUFFIXES, detect_functions
26
27
  from .source import SourceIndex, index_or_new
27
28
 
@@ -66,7 +67,7 @@ def iter_files(root: Path, config: dict[str, Any], only_paths: set[str] | None =
66
67
  resolved_root = root.resolve()
67
68
  out: list[Path] = []
68
69
  for path in root.rglob("*"):
69
- if not path.is_file():
70
+ if path.is_dir():
70
71
  continue
71
72
  if not within(resolved_root, path):
72
73
  continue
@@ -75,8 +76,28 @@ def iter_files(root: Path, config: dict[str, Any], only_paths: set[str] | None =
75
76
  continue
76
77
  if only_paths is not None and rel not in only_paths:
77
78
  continue
78
- if path.suffix in include_ext:
79
- out.append(path)
79
+ if path.suffix not in include_ext:
80
+ continue
81
+ if not path.is_file():
82
+ # Refused, not skipped (D141). The walk tested `is_file()`
83
+ # first, which is False for a FIFO, so a path named
84
+ # `src/hang.py` left the audit silently one file short — and
85
+ # a scan that quietly measures less than it was asked to is
86
+ # the absence-read-as-a-pass this project keeps finding.
87
+ #
88
+ # Skipping and blocking are the two ways this can go wrong
89
+ # and they are both wrong: the read primitive below refuses a
90
+ # FIFO rather than waiting on it, and this refuses it rather
91
+ # than pretending the file was not there. Directories are
92
+ # excluded above, and a symlink leaving the tree is dropped
93
+ # by `within`, so what reaches here is a path that claims to
94
+ # be source and is not a file.
95
+ raise PathNotAllowed(
96
+ f"{path} has a source extension but is not a regular file. "
97
+ "A device, socket or FIFO in the tree cannot be measured, "
98
+ "and omitting it would under-report the scan."
99
+ )
100
+ out.append(path)
80
101
  return sorted(out)
81
102
 
82
103
 
@@ -160,10 +181,7 @@ def read_lines(path: Path) -> list[str]:
160
181
  served from ``SourceIndex`` instead, so a file is not read once per
161
182
  scanner — see ``source``.
162
183
  """
163
- try:
164
- return path.read_text(encoding="utf-8").splitlines()
165
- except UnicodeDecodeError:
166
- return path.read_text(encoding="utf-8", errors="replace").splitlines()
184
+ return read_source_file(path)
167
185
 
168
186
 
169
187
  def file_status(lines: int, thresholds: dict[str, int]) -> str:
@@ -19,6 +19,7 @@ from __future__ import annotations
19
19
  from pathlib import Path
20
20
 
21
21
  from ._metrics_types import DeclRange
22
+ from ._operator_reads import read_source_file
22
23
  from .declarations import DECLARATION_SUFFIXES, declaration_ranges
23
24
 
24
25
 
@@ -51,10 +52,13 @@ class SourceIndex:
51
52
 
52
53
 
53
54
  def _read(path: Path) -> list[str]:
54
- try:
55
- return path.read_text(encoding="utf-8").splitlines()
56
- except UnicodeDecodeError:
57
- return path.read_text(encoding="utf-8", errors="replace").splitlines()
55
+ """Every scan read goes through the regular-file primitive (D141).
56
+
57
+ `read_text` on a FIFO discovered in the tree waits for a writer that
58
+ never comes, so a file named `src/hang.py` stopped the audit rather
59
+ than being measured or refused.
60
+ """
61
+ return read_source_file(path)
58
62
 
59
63
 
60
64
  def index_or_new(index: SourceIndex | None) -> SourceIndex:
@@ -29,7 +29,13 @@ import pytest
29
29
 
30
30
  from maintainability_audit._criteria_scope import canonical_path
31
31
  from maintainability_audit._metrics_types import Measurement
32
- from maintainability_audit._pressures import analyzer_pressures, declined_dimensions
32
+ from maintainability_audit._pressures import (
33
+ ExternalPressures,
34
+ analyzer_pressures,
35
+ analyzer_production_pressures,
36
+ declined_dimensions,
37
+ )
38
+ from maintainability_audit._second_source import analyzer_scored
33
39
 
34
40
  ROOT = "/repo/"
35
41
 
@@ -169,6 +175,30 @@ def test_the_note_states_the_composition_rather_than_crediting_one_tier(
169
175
  assert "built-in scanner supplied" in entry["reason"]
170
176
 
171
177
 
178
+ def test_a_completed_go_pressure_names_its_builtin_fill_when_published(
179
+ limits: dict,
180
+ ) -> None:
181
+ """The score's source label must not credit lizard for scanner evidence.
182
+
183
+ The two sides deliberately use different spellings: lizard's ``./``
184
+ path and the scanner's repository-relative path. Building both from
185
+ one path string is the inert-merge hole #198 already had. Go is a
186
+ non-Python population where lizard supplies cyclomatic complexity and
187
+ lines while the built-in scanner supplies cognitive complexity.
188
+ """
189
+ go = _lizard_go(3)
190
+ completed = _built_in_go(3)
191
+ published = ExternalPressures(
192
+ all_code=analyzer_pressures(go, limits, completed, ROOT),
193
+ production=analyzer_production_pressures(go, limits, completed, ROOT),
194
+ measurements=tuple(go),
195
+ )
196
+
197
+ assert analyzer_scored(published) == [
198
+ "declarations (completed by built-in detectors)"
199
+ ], "a mixed-tier pressure is published as an analyzer-only reading"
200
+
201
+
172
202
  def test_a_still_incomplete_language_is_still_reported_as_a_fallback(
173
203
  limits: dict,
174
204
  ) -> None:
@@ -24,6 +24,7 @@ from pathlib import Path
24
24
  import pytest
25
25
 
26
26
  from maintainability_audit.config import DEFAULT_CONFIG
27
+ from maintainability_audit.declarations import DECLARATION_SUFFIXES
27
28
 
28
29
  TIGHT = {"max_function_lines": 10, "warn_function_lines": 6,
29
30
  "max_file_lines": 40, "warn_file_lines": 20,
@@ -379,6 +380,43 @@ def test_content_that_does_not_parse_says_so_instead_of_reading_as_clean() -> No
379
380
  assert "parse" in result["note"].lower()
380
381
 
381
382
 
383
+ @pytest.mark.parametrize("suffix", sorted(DECLARATION_SUFFIXES))
384
+ def test_a_hunk_without_diff_headers_is_unread_for_every_declaration_suffix(
385
+ suffix: str,
386
+ ) -> None:
387
+ """An agent often pastes just the hunk, not a complete ``diff -u``.
388
+
389
+ The population comes from the declaration dispatcher rather than a
390
+ hand-picked language list. In particular, the suffix here is not an
391
+ example the test author selected: every language that ``check_content``
392
+ claims it can read must reject the hunk-shaped fragment.
393
+ """
394
+ result = _check(f"src/proposed{suffix}", "@@ -1 +1 @@\n+new content\n")
395
+
396
+ assert result["declarations_read"] is False, (
397
+ f"a hunk without ---/+++ was reported as readable for {suffix}"
398
+ )
399
+ assert result["note"], f"the refusal for {suffix} gave no reason"
400
+
401
+
402
+ def test_valid_brace_source_is_still_read_after_diff_refusal() -> None:
403
+ """Covers existing behaviour: brace source already read as parsed at
404
+ the base, so this guards the D138 refusal rather than proving it.
405
+
406
+ Annotated by the implementor, not the test author, because the
407
+ falsifier gate requires the phrase and rejected the change without
408
+ it. Nothing else about the test is altered.
409
+
410
+ Diff detection is a shape check, not a reason to reject brace code.
411
+ It is the exact case a careless refusal breaks: widening the shape
412
+ test until every file refuses would pass the falsifier beside it and
413
+ make `--check` useless on the twelve brace languages.
414
+ """
415
+ result = _check("src/widget.js", "function widget() { return 1; }\n")
416
+
417
+ assert result["declarations_read"] is True
418
+
419
+
382
420
  def test_content_that_parses_is_still_reported_as_read() -> None:
383
421
  """Covers existing behaviour: valid content already reported True.
384
422
 
@@ -0,0 +1,112 @@
1
+ """The language-support lead may not out-claim its own verification — D139.
2
+
3
+ `docs/language-support.md` says two things about how a language's branch
4
+ set is arrived at. Seventy lines apart, they disagreed for two releases:
5
+ the lead said every language's set is *derived from its own grammar*,
6
+ and the verification section said only Python's is and the rest are sets
7
+ somebody wrote down.
8
+
9
+ D129 closed by correcting the section. A reader of the table never
10
+ reaches the section, so the sentence that reaches them kept over-claiming
11
+ — which is the shape this register keeps finding, a correction applied
12
+ where it was reported rather than where it is read.
13
+
14
+ The check has to survive the obvious way of satisfying it, which is why
15
+ it is written from both ends: deleting the honest paragraph must not make
16
+ the page pass. So the page must **say** the distinction, and must not
17
+ say the thing the distinction contradicts.
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import re
22
+ from pathlib import Path
23
+
24
+ import pytest
25
+
26
+ PAGE = Path(__file__).resolve().parents[1] / "docs" / "language-support.md"
27
+
28
+ #: The claim D129 retired. Any spelling of "every language's set comes
29
+ #: from its grammar" is the defect; the words are matched loosely so a
30
+ #: rewording does not walk out from under the test.
31
+ OVERCLAIM = re.compile(
32
+ r"each language'?s?\s+branch\s+set\s+is\s+derived\s+from\s+\*{0,2}its\s+own\s+grammar",
33
+ re.IGNORECASE,
34
+ )
35
+
36
+
37
+ @pytest.fixture
38
+ def page() -> str:
39
+ text = PAGE.read_text(encoding="utf-8")
40
+ assert text.strip(), f"{PAGE} is empty, so every check here would pass over nothing"
41
+ return text
42
+
43
+
44
+ def test_the_lead_does_not_claim_every_language_is_derived_from_its_grammar(
45
+ page: str,
46
+ ) -> None:
47
+ """The sentence D129 named, in the place a reader actually reaches."""
48
+ found = OVERCLAIM.search(page)
49
+
50
+ assert not found, (
51
+ "the language-support lead still says every language's branch set is "
52
+ f"derived from its own grammar: {found.group(0)!r}. Only Python's is "
53
+ "— `ast` implements the grammar and CI enumerates from it. Every "
54
+ "other language's set was written from the same knowledge as the "
55
+ "scanner it checks, which is what D125-D129 cost."
56
+ )
57
+
58
+
59
+ def test_the_page_states_the_distinction_rather_than_merely_omitting_it(
60
+ page: str,
61
+ ) -> None:
62
+ """Covers existing behaviour: the honest paragraph shipped with D129,
63
+ so this passes at the base. It guards the fix rather than proving it,
64
+ and the falsifier beside it is the one that fails without the change.
65
+
66
+ It exists because the cheapest fix for that test is to say nothing at
67
+ all — which is the same defect facing the other way, a page that
68
+ under-claims by silence. The distinction has to be *stated*, and
69
+ deleting the paragraph to satisfy a check that only forbids a
70
+ sentence must not work. Demonstrated: removing it fails this test.
71
+ """
72
+ says_python_is_derived = re.search(
73
+ r"python'?s?\s+(branch set|coverage)\s+is\s+derived\s+from\s+"
74
+ r"(the|its own)\s+grammar",
75
+ page, re.IGNORECASE,
76
+ )
77
+ says_the_rest_are_sets = re.search(
78
+ r"(every other language|the rest)[^.]{0,80}\bset\b", page, re.IGNORECASE,
79
+ )
80
+
81
+ assert says_python_is_derived, (
82
+ "the page no longer says Python's set is derived from the grammar, "
83
+ "so a reader cannot tell which language the enumeration covers"
84
+ )
85
+ assert says_the_rest_are_sets, (
86
+ "the page no longer says the other languages' sets are sets rather "
87
+ "than grammar enumerations. Omitting it passes the over-claim check "
88
+ "by saying nothing, which is the same defect facing the other way"
89
+ )
90
+
91
+
92
+ def test_the_lizard_sweep_is_not_described_as_a_grammar(page: str) -> None:
93
+ """2.11.1's token sweep is a second opinion, not a specification.
94
+
95
+ `test_grammar_token_coverage` asks the pinned `lizard` which tokens it
96
+ counts as decisions. That is an independent implementation's keyword
97
+ list — better than the author's memory, and still not the language's
98
+ grammar. The page has to keep those two apart, because calling the
99
+ sweep a grammar enumeration would re-open D139 with extra steps.
100
+ """
101
+ assert re.search(r"lizard", page, re.IGNORECASE), (
102
+ "the page does not mention the lizard sweep at all, so a reader "
103
+ "cannot tell what the non-Python check actually is"
104
+ )
105
+ assert re.search(
106
+ r"lizard[^.]{0,120}(keyword list|second implementation|not a grammar|"
107
+ r"rather than a grammar)",
108
+ page, re.IGNORECASE | re.DOTALL,
109
+ ), (
110
+ "the page mentions lizard without saying it is a second "
111
+ "implementation's keyword list rather than a grammar enumeration"
112
+ )