maintainability-agent 2.11.2__tar.gz → 3.0.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 (356) hide show
  1. {maintainability_agent-2.11.2/src/maintainability_agent.egg-info → maintainability_agent-3.0.0}/PKG-INFO +5 -3
  2. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/README.md +4 -2
  3. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/pyproject.toml +1 -1
  4. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0/src/maintainability_agent.egg-info}/PKG-INFO +5 -3
  5. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_agent.egg-info/SOURCES.txt +5 -0
  6. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/__init__.py +1 -1
  7. maintainability_agent-3.0.0/src/maintainability_audit/_analyzer_sections.py +85 -0
  8. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_calibration.py +89 -5
  9. maintainability_agent-3.0.0/src/maintainability_audit/_criteria_scope.py +279 -0
  10. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_formula.py +12 -2
  11. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_pressures.py +153 -68
  12. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/config.py +1 -1
  13. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/report.py +23 -61
  14. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/scoring.py +3 -2
  15. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_analyzer_scoring_path.py +2 -2
  16. maintainability_agent-3.0.0/tests/test_criteria_completion.py +182 -0
  17. maintainability_agent-3.0.0/tests/test_declaration_criteria_scope.py +312 -0
  18. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_environment_work_order.py +1 -1
  19. maintainability_agent-3.0.0/tests/test_grammar_token_coverage.py +209 -0
  20. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_pool_runs_by_config.py +35 -5
  21. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_stage8_contract.py +6 -0
  22. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_three_presentations.py +1 -1
  23. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_verified_grade.py +4 -3
  24. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_version_claim.py +9 -2
  25. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/LICENSE +0 -0
  26. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/setup.cfg +0 -0
  27. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_agent.egg-info/dependency_links.txt +0 -0
  28. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_agent.egg-info/entry_points.txt +0 -0
  29. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_agent.egg-info/requires.txt +0 -0
  30. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_agent.egg-info/top_level.txt +0 -0
  31. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/__main__.py +0 -0
  32. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_adapters.py +0 -0
  33. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_analysis.py +0 -0
  34. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_anchor.py +0 -0
  35. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_arguments.py +0 -0
  36. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_aspects.py +0 -0
  37. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_assets/analyzer-catalog.json +0 -0
  38. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_assets/standard.md +0 -0
  39. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_attestation.py +0 -0
  40. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_backfill.py +0 -0
  41. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_bands.py +0 -0
  42. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_banner.py +0 -0
  43. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_built_ins.py +0 -0
  44. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_catalog.py +0 -0
  45. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_charts.py +0 -0
  46. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_cognitive.py +0 -0
  47. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_config_defaults.py +0 -0
  48. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_conformance.py +0 -0
  49. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_corroborate.py +0 -0
  50. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_coverage_notes.py +0 -0
  51. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_derive.py +0 -0
  52. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_discovery.py +0 -0
  53. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_documents.py +0 -0
  54. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_economics.py +0 -0
  55. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_economics_view.py +0 -0
  56. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_environment.py +0 -0
  57. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_evidence_view.py +0 -0
  58. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_finding_match.py +0 -0
  59. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_first_run.py +0 -0
  60. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_gates.py +0 -0
  61. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_generic.py +0 -0
  62. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_grant_ledger.py +0 -0
  63. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_history_view.py +0 -0
  64. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_hostile_prompt.py +0 -0
  65. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_hotspots.py +0 -0
  66. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_html_report_sections.py +0 -0
  67. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_html_view.py +0 -0
  68. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_identity.py +0 -0
  69. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_in_loop.py +0 -0
  70. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_in_loop_view.py +0 -0
  71. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_jvm_adapters.py +0 -0
  72. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_markdown_sections.py +0 -0
  73. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_masking.py +0 -0
  74. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_mcp_audit.py +0 -0
  75. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_mcp_gate.py +0 -0
  76. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_mcp_grants.py +0 -0
  77. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_mcp_refusals.py +0 -0
  78. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_mcp_resources.py +0 -0
  79. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_mcp_setup.py +0 -0
  80. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_metric_adapters.py +0 -0
  81. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_metrics_types.py +0 -0
  82. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_operator_reads.py +0 -0
  83. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_pillars.py +0 -0
  84. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_practice.py +0 -0
  85. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_precommit.py +0 -0
  86. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_precommit_install.py +0 -0
  87. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_precommit_view.py +0 -0
  88. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_ranges_c.py +0 -0
  89. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_ranges_cobol.py +0 -0
  90. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_ranges_core.py +0 -0
  91. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_ranges_cpp.py +0 -0
  92. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_ranges_csharp.py +0 -0
  93. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_ranges_fortran.py +0 -0
  94. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_ranges_go.py +0 -0
  95. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_ranges_java.py +0 -0
  96. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_ranges_js.py +0 -0
  97. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_ranges_php.py +0 -0
  98. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_ranges_ruby.py +0 -0
  99. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_ranges_rust.py +0 -0
  100. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_ranges_swift.py +0 -0
  101. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_ratchet.py +0 -0
  102. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_recurrence.py +0 -0
  103. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_run_comparison.py +0 -0
  104. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_runner.py +0 -0
  105. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_safe_write.py +0 -0
  106. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_scan_history.py +0 -0
  107. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_scan_view.py +0 -0
  108. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_second_source.py +0 -0
  109. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_selection.py +0 -0
  110. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_semantic.py +0 -0
  111. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_semantic_policy.py +0 -0
  112. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_semantic_ts.py +0 -0
  113. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_semantic_view.py +0 -0
  114. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_setup_errors.py +0 -0
  115. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_setup_persist.py +0 -0
  116. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_skill_data/SKILL.md +0 -0
  117. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_skill_data/agents/anthropic.yaml +0 -0
  118. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_skill_data/agents/copilot.yaml +0 -0
  119. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_skill_data/agents/openai.yaml +0 -0
  120. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_skill_data/copilot/maintainability-agent.prompt.md +0 -0
  121. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_skill_data/references/finding-taxonomy.md +0 -0
  122. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_skill_install.py +0 -0
  123. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_stored_grants.py +0 -0
  124. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_tdd_view.py +0 -0
  125. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_test_commands.py +0 -0
  126. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_test_execution.py +0 -0
  127. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_test_pairing.py +0 -0
  128. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_tokens.py +0 -0
  129. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_tool_adapters.py +0 -0
  130. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_trends.py +0 -0
  131. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_user_config.py +0 -0
  132. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_verdict_adapters.py +0 -0
  133. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_verification.py +0 -0
  134. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_work_order.py +0 -0
  135. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_work_order_view.py +0 -0
  136. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_work_order_weights.py +0 -0
  137. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/_xml.py +0 -0
  138. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/baseline.py +0 -0
  139. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/cli.py +0 -0
  140. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/deadcode.py +0 -0
  141. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/declarations.py +0 -0
  142. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/duplication.py +0 -0
  143. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/evidence.py +0 -0
  144. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/git_tools.py +0 -0
  145. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/history.py +0 -0
  146. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/idioms.py +0 -0
  147. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/instructions.py +0 -0
  148. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/mcp_server.py +0 -0
  149. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/metrics.py +0 -0
  150. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/prompts.py +0 -0
  151. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/renderers.py +0 -0
  152. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/sarif.py +0 -0
  153. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/similarity.py +0 -0
  154. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/src/maintainability_audit/source.py +0 -0
  155. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_acquisition_trust.py +0 -0
  156. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_adapter_docs.py +0 -0
  157. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_adapter_recordings.py +0 -0
  158. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_adapters.py +0 -0
  159. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_added_lines.py +0 -0
  160. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_analysis_coverage.py +0 -0
  161. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_analysis_execution.py +0 -0
  162. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_analyzer_bridge.py +0 -0
  163. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_analyzer_config_isolation.py +0 -0
  164. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_analyzer_estimate_claims.py +0 -0
  165. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_analyzer_pinning.py +0 -0
  166. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_analyzer_primary_estimate.py +0 -0
  167. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_analyzer_provenance.py +0 -0
  168. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_analyzer_provenance_exclusions.py +0 -0
  169. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_analyzer_spread_range.py +0 -0
  170. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_analyzer_xml_bounds.py +0 -0
  171. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_anchor_and_mode.py +0 -0
  172. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_anchor_disclosure.py +0 -0
  173. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_annotations_match_behaviour.py +0 -0
  174. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_anticipated_refusals.py +0 -0
  175. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_architecture.py +0 -0
  176. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_artifact_write_route_class.py +0 -0
  177. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_aspect_coverage.py +0 -0
  178. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_asset_snapshot_class.py +0 -0
  179. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_ast_reading_resolves_spellings.py +0 -0
  180. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_attestation.py +0 -0
  181. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_audit_components.py +0 -0
  182. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_authorization_freshness.py +0 -0
  183. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_authorship_gates.py +0 -0
  184. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_backfill.py +0 -0
  185. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_band_concealment_class.py +0 -0
  186. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_band_concealment_p3.py +0 -0
  187. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_band_pressures.py +0 -0
  188. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_bands.py +0 -0
  189. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_body_or_signature_class.py +0 -0
  190. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_c_declarations.py +0 -0
  191. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_calibration_corpus.py +0 -0
  192. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_calibration_drift.py +0 -0
  193. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_calibration_tooling.py +0 -0
  194. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_catalog_selection.py +0 -0
  195. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_catalog_tier_promise.py +0 -0
  196. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_chat_primary_docs.py +0 -0
  197. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_checkstyle_adapter.py +0 -0
  198. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_checkstyle_catalog.py +0 -0
  199. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_ci_history_recipe.py +0 -0
  200. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_ci_installs_the_analyzer_pool.py +0 -0
  201. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_claimed_languages.py +0 -0
  202. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_cli.py +0 -0
  203. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_clone_group_class.py +0 -0
  204. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_cobol_declarations.py +0 -0
  205. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_cognitive_complexity.py +0 -0
  206. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_cohort_statistics.py +0 -0
  207. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_concealment_properties.py +0 -0
  208. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_config_shape.py +0 -0
  209. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_consent_and_grant.py +0 -0
  210. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_consumer_migration.py +0 -0
  211. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_contract_claims.py +0 -0
  212. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_corroborate.py +0 -0
  213. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_coverage_by_language.py +0 -0
  214. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_cpp_declarations.py +0 -0
  215. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_csharp_declarations.py +0 -0
  216. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_d15_composition.py +0 -0
  217. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_d15_goal_directed.py +0 -0
  218. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_dead_code.py +0 -0
  219. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_declaration_edge_cases.py +0 -0
  220. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_declaration_grading.py +0 -0
  221. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_declaration_ranges.py +0 -0
  222. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_declaration_source.py +0 -0
  223. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_declared_authorship.py +0 -0
  224. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_declared_imports.py +0 -0
  225. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_derivation_mix.py +0 -0
  226. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_determinism.py +0 -0
  227. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_dimension_ratchet.py +0 -0
  228. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_direct_runner_availability_class.py +0 -0
  229. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_discovery.py +0 -0
  230. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_doc_claims.py +0 -0
  231. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_docs_links.py +0 -0
  232. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_economic_context.py +0 -0
  233. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_empty_output_not_clean.py +0 -0
  234. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_empty_output_not_clean_adapters.py +0 -0
  235. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_evidence_invariants.py +0 -0
  236. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_evidence_normalization.py +0 -0
  237. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_evidence_properties.py +0 -0
  238. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_exclusion_dialects.py +0 -0
  239. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_falsifier_standard.py +0 -0
  240. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_file_list_cap_class.py +0 -0
  241. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_finding_identity.py +0 -0
  242. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_first_run_elicitation.py +0 -0
  243. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_first_run_prompt.py +0 -0
  244. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_first_run_reelicitation.py +0 -0
  245. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_fix_breadth_window.py +0 -0
  246. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_fixed_form_fortran.py +0 -0
  247. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_format_ask.py +0 -0
  248. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_fortran_declarations.py +0 -0
  249. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_fortran_metrics.py +0 -0
  250. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_fortran_practice.py +0 -0
  251. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_git_argv.py +0 -0
  252. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_git_read_only.py +0 -0
  253. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_git_readonly_class.py +0 -0
  254. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_go_declarations.py +0 -0
  255. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_grammar_constructs.py +0 -0
  256. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_grant_only_user_tier.py +0 -0
  257. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_history.py +0 -0
  258. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_history_boundary.py +0 -0
  259. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_history_manifest.py +0 -0
  260. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_history_schema2.py +0 -0
  261. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_history_window.py +0 -0
  262. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_hostile_prompt.py +0 -0
  263. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_html_executive.py +0 -0
  264. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_html_section_parity_class.py +0 -0
  265. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_identity_docs.py +0 -0
  266. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_identity_resolution.py +0 -0
  267. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_idiom_divergence.py +0 -0
  268. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_in_loop_budgets.py +0 -0
  269. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_in_loop_check.py +0 -0
  270. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_installed_wheel_runtime.py +0 -0
  271. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_instruction_targets.py +0 -0
  272. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_java_range_guard.py +0 -0
  273. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_java_ranges.py +0 -0
  274. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_java_scored.py +0 -0
  275. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_java_wiring.py +0 -0
  276. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_js_complexity_operators.py +0 -0
  277. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_js_declarations.py +0 -0
  278. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_language_coverage.py +0 -0
  279. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_mcp_baseline_payload.py +0 -0
  280. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_mcp_history.py +0 -0
  281. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_mcp_server.py +0 -0
  282. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_migration_1_0.py +0 -0
  283. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_near_duplicates.py +0 -0
  284. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_network_disclosure.py +0 -0
  285. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_one_setup_across_surfaces_class.py +0 -0
  286. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_operator_named_paths.py +0 -0
  287. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_operator_named_reads.py +0 -0
  288. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_pattern_budget.py +0 -0
  289. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_phase6_claims.py +0 -0
  290. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_php_declarations.py +0 -0
  291. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_pillars_and_practice.py +0 -0
  292. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_platform_claim.py +0 -0
  293. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_pmd_adapter.py +0 -0
  294. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_pmd_audit_closures.py +0 -0
  295. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_policy_claims.py +0 -0
  296. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_population_floors.py +0 -0
  297. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_practice_nested_gates_class.py +0 -0
  298. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_precommit_staged.py +0 -0
  299. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_presentation_refusals.py +0 -0
  300. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_promises.py +0 -0
  301. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_prompt_escalation_once.py +0 -0
  302. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_prompt_honesty.py +0 -0
  303. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_prompt_location_escalation_class.py +0 -0
  304. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_prove_falsifiers.py +0 -0
  305. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_python_complexity.py +0 -0
  306. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_python_declarations.py +0 -0
  307. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_read_boundary.py +0 -0
  308. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_readme_claims.py +0 -0
  309. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_recurrence.py +0 -0
  310. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_release_plan.py +0 -0
  311. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_release_plan_status.py +0 -0
  312. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_report_remedy_copy.py +0 -0
  313. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_roles_recorded.py +0 -0
  314. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_root_grants.py +0 -0
  315. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_rubric_ignores_project_lint_config.py +0 -0
  316. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_ruby_declarations.py +0 -0
  317. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_run_comparison.py +0 -0
  318. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_runner.py +0 -0
  319. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_rust_declarations.py +0 -0
  320. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_sarif.py +0 -0
  321. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_scan_history.py +0 -0
  322. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_scan_scope.py +0 -0
  323. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_scanner_unsafe_direction.py +0 -0
  324. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_scanning.py +0 -0
  325. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_schema_fidelity.py +0 -0
  326. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_scope_conformance.py +0 -0
  327. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_scoring_calibration.py +0 -0
  328. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_semantic_policy.py +0 -0
  329. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_setup_gate_completeness.py +0 -0
  330. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_setup_gate_independent_contract.py +0 -0
  331. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_setup_precondition.py +0 -0
  332. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_skill_install.py +0 -0
  333. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_sonar_resolve.py +0 -0
  334. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_source_index.py +0 -0
  335. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_spotbugs_adapter.py +0 -0
  336. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_spotbugs_catalog.py +0 -0
  337. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_suite_isolation.py +0 -0
  338. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_swift_declarations.py +0 -0
  339. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_tdd_structure.py +0 -0
  340. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_test_commands.py +0 -0
  341. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_test_execution_class.py +0 -0
  342. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_tree_chosen_spawn.py +0 -0
  343. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_trends.py +0 -0
  344. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_type_fields_not_functions_class.py +0 -0
  345. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_uat_falsifiers.py +0 -0
  346. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_unread_code.py +0 -0
  347. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_user_config_tier.py +0 -0
  348. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_verification_audit_closures.py +0 -0
  349. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_wheel_contents.py +0 -0
  350. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_work_order.py +0 -0
  351. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_work_order_items.py +0 -0
  352. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_work_order_selection.py +0 -0
  353. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_workflow_supply_chain.py +0 -0
  354. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_write_boundary.py +0 -0
  355. {maintainability_agent-2.11.2 → maintainability_agent-3.0.0}/tests/test_write_route_class.py +0 -0
  356. {maintainability_agent-2.11.2 → maintainability_agent-3.0.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.11.2
3
+ Version: 3.0.0
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 **2.11.2**.
48
+ Version **3.0.0**.
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
@@ -397,7 +397,9 @@ findings — is in the report itself.
397
397
  ## Platform support
398
398
 
399
399
  **POSIX. Windows is not claimed, for a measured reason.** A `windows-latest` probe
400
- found most failures in two POSIX-only calls, `os.fchmod` and `os.O_DIRECTORY`. The
400
+ found most failures in three POSIX-only calls: `os.fchmod`, `os.O_DIRECTORY` and
401
+ `os.O_NONBLOCK`. The third arrived with the one-handle operator read (D130/D131)
402
+ and is now the dominant failure in the probe, which is why the count moved. The
401
403
  bounded, symlink-refusing write works through a *file descriptor*, so a validated
402
404
  path cannot be swapped for a symlink before the write lands — Windows has no
403
405
  equivalent and the portable rewrite is the hole. **An unsupported platform never
@@ -5,7 +5,7 @@
5
5
  **A deterministic, offline maintainability audit whose output is a _bounded
6
6
  work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
7
7
  says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
8
- Version **2.11.2**.
8
+ Version **3.0.0**.
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
@@ -357,7 +357,9 @@ findings — is in the report itself.
357
357
  ## Platform support
358
358
 
359
359
  **POSIX. Windows is not claimed, for a measured reason.** A `windows-latest` probe
360
- found most failures in two POSIX-only calls, `os.fchmod` and `os.O_DIRECTORY`. The
360
+ found most failures in three POSIX-only calls: `os.fchmod`, `os.O_DIRECTORY` and
361
+ `os.O_NONBLOCK`. The third arrived with the one-handle operator read (D130/D131)
362
+ and is now the dominant failure in the probe, which is why the count moved. The
361
363
  bounded, symlink-refusing write works through a *file descriptor*, so a validated
362
364
  path cannot be swapped for a symlink before the write lands — Windows has no
363
365
  equivalent and the portable rewrite is the hole. **An unsupported platform never
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "maintainability-agent"
7
- version = "2.11.2"
7
+ version = "3.0.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.11.2
3
+ Version: 3.0.0
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 **2.11.2**.
48
+ Version **3.0.0**.
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
@@ -397,7 +397,9 @@ findings — is in the report itself.
397
397
  ## Platform support
398
398
 
399
399
  **POSIX. Windows is not claimed, for a measured reason.** A `windows-latest` probe
400
- found most failures in two POSIX-only calls, `os.fchmod` and `os.O_DIRECTORY`. The
400
+ found most failures in three POSIX-only calls: `os.fchmod`, `os.O_DIRECTORY` and
401
+ `os.O_NONBLOCK`. The third arrived with the one-handle operator read (D130/D131)
402
+ and is now the dominant failure in the probe, which is why the count moved. The
401
403
  bounded, symlink-refusing write works through a *file descriptor*, so a validated
402
404
  path cannot be swapped for a symlink before the write lands — Windows has no
403
405
  equivalent and the portable rewrite is the hole. **An unsupported platform never
@@ -11,6 +11,7 @@ src/maintainability_audit/__init__.py
11
11
  src/maintainability_audit/__main__.py
12
12
  src/maintainability_audit/_adapters.py
13
13
  src/maintainability_audit/_analysis.py
14
+ src/maintainability_audit/_analyzer_sections.py
14
15
  src/maintainability_audit/_anchor.py
15
16
  src/maintainability_audit/_arguments.py
16
17
  src/maintainability_audit/_aspects.py
@@ -27,6 +28,7 @@ src/maintainability_audit/_config_defaults.py
27
28
  src/maintainability_audit/_conformance.py
28
29
  src/maintainability_audit/_corroborate.py
29
30
  src/maintainability_audit/_coverage_notes.py
31
+ src/maintainability_audit/_criteria_scope.py
30
32
  src/maintainability_audit/_derive.py
31
33
  src/maintainability_audit/_discovery.py
32
34
  src/maintainability_audit/_documents.py
@@ -199,10 +201,12 @@ tests/test_contract_claims.py
199
201
  tests/test_corroborate.py
200
202
  tests/test_coverage_by_language.py
201
203
  tests/test_cpp_declarations.py
204
+ tests/test_criteria_completion.py
202
205
  tests/test_csharp_declarations.py
203
206
  tests/test_d15_composition.py
204
207
  tests/test_d15_goal_directed.py
205
208
  tests/test_dead_code.py
209
+ tests/test_declaration_criteria_scope.py
206
210
  tests/test_declaration_edge_cases.py
207
211
  tests/test_declaration_grading.py
208
212
  tests/test_declaration_ranges.py
@@ -241,6 +245,7 @@ tests/test_git_read_only.py
241
245
  tests/test_git_readonly_class.py
242
246
  tests/test_go_declarations.py
243
247
  tests/test_grammar_constructs.py
248
+ tests/test_grammar_token_coverage.py
244
249
  tests/test_grant_only_user_tier.py
245
250
  tests/test_history.py
246
251
  tests/test_history_boundary.py
@@ -1,3 +1,3 @@
1
1
  """Maintainability Audit CI."""
2
2
 
3
- __version__ = "2.11.2"
3
+ __version__ = "3.0.0"
@@ -0,0 +1,85 @@
1
+ """The analyzer half of a report, and the join that completes it.
2
+
3
+ Split from `report.py` at the 500-line gate this tool enforces on
4
+ everyone else. The seam is real rather than arithmetic: everything here
5
+ is about the external analyzer tier — what it measured, what it could
6
+ not, and how its criterion set is completed from the built-in scanner so
7
+ a language no tool reads cognitively can still be analyzer-driven.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ from ._analysis import analyze
15
+ from ._criteria_scope import built_in_readings
16
+ from ._documents import (
17
+ coverage_document,
18
+ findings_document,
19
+ measurement_document,
20
+ unidentified_source_paths,
21
+ )
22
+ from ._environment import environment_work_order
23
+ from ._metrics_types import FunctionMetric
24
+ from ._pressures import (
25
+ ExternalPressures,
26
+ analyzer_pressures,
27
+ analyzer_production_pressures,
28
+ declined_dimensions,
29
+ )
30
+
31
+
32
+ def analyzer_sections(
33
+ root: Path,
34
+ config: dict[str, Any],
35
+ run_analyzers: bool,
36
+ function_metrics: list[FunctionMetric],
37
+ ) -> dict[str, Any]:
38
+ """The three analyzer sections, or their empty forms.
39
+
40
+ Extracted because `build_report` reached complexity 15 against this
41
+ project's own limit. Four conditionals all asking the same question
42
+ belong behind one, and the empty forms are stated here rather than
43
+ repeated at each call site.
44
+ """
45
+ if not run_analyzers:
46
+ return {"coverage": None, "findings": [], "measurements": {},
47
+ "pressures": None, "environment": []}
48
+ analysis = analyze(root, config)
49
+ coverage = coverage_document(analysis)
50
+ # P8: where the analyzer tier could not drive a dimension, say so on
51
+ # the coverage document rather than leaving the built-in fallback to
52
+ # be inferred from an absent number. Attached here because only this
53
+ # layer holds both the measurements and the rubric's thresholds.
54
+ # The scanner's own reading of each declaration, so the analyzer tier
55
+ # can complete a criterion set no single tool covers (per concept,
56
+ # not per dimension). Keyed the way the join is proven: (path, line).
57
+ prefix = root.as_posix().rstrip("/") + "/"
58
+ built_in = built_in_readings(function_metrics, prefix)
59
+ coverage["dimensions_declined"] = list(
60
+ declined_dimensions(analysis.measurements, False, built_in, prefix))
61
+ return {
62
+ "coverage": coverage,
63
+ # ADR 006 §2c: what did not run and what it would take, for the
64
+ # user to act on. Emitted here because only the analysis knows
65
+ # which tools were *selected*; the agent never runs the commands.
66
+ "environment": environment_work_order(analysis.coverage),
67
+ "findings": findings_document(analysis, root),
68
+ # Path spellings normalization refused (zero or several matches)
69
+ # — visible, so a refusal is never mistaken for an identification.
70
+ "unidentified_source_paths": unidentified_source_paths(analysis, root),
71
+ "measurements": measurement_document(analysis, root),
72
+ # The analyzers' reading of the scorer's dimensions, primary for
73
+ # each it covers (ADR 006 §1); where None the built-in stands, so
74
+ # an unmeasured dimension is unmeasured, not clean. Both
75
+ # populations, since the production aspects read this.
76
+ "pressures": ExternalPressures(
77
+ all_code=analyzer_pressures(
78
+ analysis.measurements, config["thresholds"], built_in, prefix),
79
+ production=analyzer_production_pressures(
80
+ analysis.measurements, config["thresholds"], built_in, prefix),
81
+ # The raw readings ride along so the scorer can price
82
+ # per-concept tool disagreement into the range (3.4).
83
+ measurements=tuple(analysis.measurements),
84
+ ),
85
+ }
@@ -107,11 +107,94 @@ CATEGORIES = ["modularity", "reusability", "analyzability", "modifiability", "te
107
107
  # the wrong unit of analysis; the languages do not behave as a bloc, and a
108
108
  # single dramatic repository predicted the direction of the whole corpus
109
109
  # incorrectly. Per-language readings are in `docs/calibration-2.0-study.md`.
110
+ #
111
+ # **2026-09-07, re-measured; the constants held.** The stored rows had been
112
+ # measured at tool versions 1.10.1 and 2.0.0 under scanner fingerprint
113
+ # 3b5e063e95316342. HEAD is 2.11.2 under 1a902618a0600746 — eleven minor
114
+ # releases of scanner work later, including four new language scanners and
115
+ # the D112-D136 fixes. All 112 repositories were re-measured
116
+ # `--with-analyzers` at the same pinned commits, so every move below is
117
+ # attributable to the scanners and to nothing else:
118
+ #
119
+ # file_size 0.0952 -> 0.0952 unchanged
120
+ # declarations 0.0908 -> 0.0903 92 of 112 rows moved
121
+ # duplication 0.3222 -> 0.3222 unchanged
122
+ # risk 0.0826 -> 0.0811 21 rows moved
123
+ # gates 0.05 -> 0.05 fixed, not measured
124
+ # CALIBRATION_C 8.7161 -> 8.7161 re-bisected to the same value
125
+ #
126
+ # The constants are deliberately left as they stand. Both movers sit well
127
+ # inside the 10% the suite tolerates (0.55% and 1.8%), and the curve
128
+ # re-fitted to four identical decimals, so editing them would change the
129
+ # published numbers by less than the sampling error in the numbers
130
+ # themselves - the argument `test_the_curve_constant_still_does_its_job`
131
+ # already makes for c.
132
+ #
133
+ # **No repository changes grade.** Old corpus and new, under the shipped
134
+ # constants and under the re-derived ones, all give A=5, B=52, C=55 with a
135
+ # median of exactly 4.0000. Two repositories move at the published decimal
136
+ # at all: hoppscotch 3.6 -> 3.7 and googletest 4.1 -> 4.0.
137
+ #
138
+ # This is worth recording because the roadmap predicted the opposite - that
139
+ # the recalibration "re-grades every repository, which is a breaking change
140
+ # to every published number". For *scanner* drift that is now measured, and
141
+ # it is false: 92 of 112 declaration readings moved underneath an anchor
142
+ # that did not. It says nothing about extending the corpus to hold the six
143
+ # unanchored languages, which moves the population the medians are drawn
144
+ # from rather than the instrument reading it. That half remains untested.
145
+ #
146
+ # **2026-09-08, the recalibration (3.0.0).** The one this corpus policy
147
+ # scheduled, and the first that re-grades repositories. Three things
148
+ # changed at once and the release exists because they cannot be
149
+ # separated — each moves `scanner_fingerprint` and invalidates every
150
+ # stored row.
151
+ #
152
+ # *The corpus grew to 180.* Swift, Go, Rust, PHP and Ruby are anchored,
153
+ # fourteen repositories each but for Ruby's twelve. COBOL is not, and
154
+ # will not be: one public repository clears 500 stars and what exists is
155
+ # curriculum, Java parsers and JavaScript wrappers rather than COBOL
156
+ # codebases. The 112 existing rows keep their pinned commits.
157
+ #
158
+ # *The analyzer tier now drives declarations almost everywhere.* Under
159
+ # 2.11.2 the criterion set could only be completed for Python, so 6 rows
160
+ # of 180 carried an analyzer reading. Completing it from the built-in
161
+ # tier per concept — `lizard` for cyclomatic complexity and declaration
162
+ # lines, the scanner for the cognitive complexity no FOSS tool supplies
163
+ # — takes that to **173 of 180**.
164
+ #
165
+ # *D137 fixed the criterion check's scope*, so one Python file can no
166
+ # longer buy the criterion for a repository's C.
167
+ #
168
+ # file_size 0.0952 -> 0.0938
169
+ # declarations 0.0908 -> 0.0962
170
+ # duplication 0.3222 -> 0.3144
171
+ # risk 0.0826 -> 0.0341
172
+ # gates 0.05 -> 0.05
173
+ # CALIBRATION_C 8.7161 -> 4.2565
174
+ #
175
+ # **`risk` more than halved, and the languages explain it.** Risk
176
+ # patterns are configured policy and this project's defaults were written
177
+ # against Python and C. Measured as a multiple of the new reference, the
178
+ # five added languages read swift 0.00x, php 0.01x, ruby 0.04x, go 0.10x
179
+ # and rust 0.26x, against c 5.82x, cpp 4.95x and python 4.24x. The median
180
+ # fell because the corpus stopped being dominated by the two ecosystems
181
+ # the default patterns were written for. That is the anchor working: the
182
+ # old reference was measuring the pattern set, not the code.
183
+ #
184
+ # **This release re-grades repositories, which the previous one did not.**
185
+ # 33 of the 112 repositories in both corpora change grade — 15 B->C,
186
+ # 7 C->B, 5 A->B, 4 C->D and 2 B->A — and the corpus gains its first D
187
+ # grades. The 2026-09-07 re-measure moved nothing and said so; this one
188
+ # moves a third of them. Both statements are measured, which is why only
189
+ # this one is a major version.
190
+ #
191
+ # The median still rolls up to exactly 4.0000, which is the anchor's
192
+ # whole job and the one thing that must not move.
110
193
  DIMENSION_REFERENCES: dict[str, float] = {
111
- "file_size": 0.0952,
112
- "declarations": 0.0908,
113
- "duplication": 0.3222,
114
- "risk": 0.0826,
194
+ "file_size": 0.0938,
195
+ "declarations": 0.0962,
196
+ "duplication": 0.3144,
197
+ "risk": 0.0341,
115
198
  # Fixed, not corpus-derived — see ``_derive.FIXED_REFERENCES``, which
116
199
  # is the authority for this value and carries the reasoning. Stated
117
200
  # again here rather than imported, so the two are independent claims
@@ -175,7 +258,8 @@ DIMENSION_WEIGHTS: dict[str, float] = {
175
258
  # 2026-08-14: 2.6279 -> 2.2658, fitted against the analyzer-primary mix.
176
259
  # 2026-08-31: 2.2658 -> 5.8843, re-fitted after the corpus re-measure above
177
260
  # (three of five references moved at once, `duplication` most of all).
178
- CALIBRATION_C = 8.7161
261
+ # 2026-09-08: 8.7161 -> 4.2565, the 3.0.0 recalibration above.
262
+ CALIBRATION_C = 4.2565
179
263
 
180
264
  # A failure is a threshold breach; a warning is an approach to one.
181
265
  WARN_WEIGHT = 0.3
@@ -0,0 +1,279 @@
1
+ """Which declarations the analyzer tier may score — the D137 seam.
2
+
3
+ Split out of `_pressures.py` when that module crossed the 500-line gate
4
+ this tool enforces on everyone else. The seam is real rather than
5
+ arithmetic: everything here answers one question — *may this declaration
6
+ be scored by an analyzer reading at all?* — and nothing else answers it.
7
+
8
+ That question has been got wrong five times. `test_analyzer_bridge`
9
+ records the first four, each a ratio published before the comparison
10
+ could be trusted. The fifth, D137, was this: the completeness check took
11
+ a union of concepts across the whole repository while the units it gated
12
+ were scored one at a time, so a C repository holding a single `setup.py`
13
+ had its C declarations scored on two criteria of three. Keeping the
14
+ decision in one module, returning the population rather than a verdict,
15
+ is what stops a sixth from arriving by scoping the check differently
16
+ from the thing it gates.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import posixpath
21
+ from collections import defaultdict
22
+ from pathlib import PurePosixPath
23
+
24
+ from ._metrics_types import KNOWN_SOURCE_SUFFIXES, Measurement
25
+
26
+ # The concepts the `declarations` dimension needs, and the rubric
27
+ # thresholds that decide a breach. All three, because `function_status`
28
+ # fails a declaration on **lines OR complexity OR cognitive complexity**
29
+ # — a bridge counting only complexity measures something narrower and
30
+ # cannot be compared against it. Three ratios were quoted from that
31
+ # mistake before a test comparing every criterion caught it.
32
+ DECLARATION_CRITERIA: tuple[tuple[str, str, str], ...] = (
33
+ ("cyclomatic_complexity", "warn_complexity", "max_complexity"),
34
+ ("declaration_lines", "warn_function_lines", "max_function_lines"),
35
+ ("cognitive_complexity", "warn_cognitive_complexity", "max_cognitive_complexity"),
36
+ )
37
+
38
+ # Dimensions an analyzer can supply at all. `file_size` needs per-file
39
+ # line counts no permissively-licensed tool in the pool reports, and
40
+ # `risk` and `gates` are configured policy with no external equivalent.
41
+ ANALYZER_DIMENSIONS: tuple[str, ...] = ("declarations",)
42
+
43
+
44
+ def declaration_concepts_missing(covered: set[str]) -> tuple[str, ...]:
45
+ """Criteria the analyzers did not supply, in rubric order.
46
+
47
+ The fallback this decides is correct and used to be silent, which P8
48
+ forbids: the reader saw a declarations rate with nothing saying what
49
+ produced it. It is not a rare path either. lizard reports cyclomatic
50
+ complexity and declaration lines and no cognitive complexity, so a
51
+ JavaScript repository with lizard installed and nothing else takes
52
+ this branch on *every* run -- by construction, and an audit had to
53
+ point out that the page justifying JavaScript support credited the
54
+ analyzer pool for work the built-in scanner was doing.
55
+ """
56
+ return tuple(
57
+ concept for concept, _warn, _fail in DECLARATION_CRITERIA
58
+ if concept not in covered
59
+ )
60
+
61
+
62
+ def _unit_languages(measurements: list[Measurement]) -> dict[str, str]:
63
+ """Each unit's language, by the suffix of the file it was read from.
64
+
65
+ Units whose suffix names no language this project knows are left out
66
+ rather than pooled under one unknown bucket. The gate below asks
67
+ whether a *language* is missing a criterion, and a question about a
68
+ language cannot be asked of a file whose language was never
69
+ established — pooling them would let `pmd`'s XML readings, which
70
+ carry no declaration lines, fail the check for a Java repository
71
+ that is otherwise completely measured.
72
+ """
73
+ languages: dict[str, str] = {}
74
+ for measurement in measurements:
75
+ path = measurement.path or measurement.unit
76
+ suffix = PurePosixPath(path).suffix
77
+ language = KNOWN_SOURCE_SUFFIXES.get(suffix)
78
+ if language:
79
+ languages[measurement.unit] = language
80
+ return languages
81
+
82
+
83
+ def _concepts_by_language(
84
+ per_unit: dict[str, dict[str, float]], unit_languages: dict[str, str]
85
+ ) -> dict[str, set[str]]:
86
+ """What the analyzers measured, gathered per language."""
87
+ covered: dict[str, set[str]] = defaultdict(set)
88
+ for unit, values in per_unit.items():
89
+ language = unit_languages.get(unit)
90
+ if language:
91
+ covered[language].update(values)
92
+ return dict(covered)
93
+
94
+
95
+ def _languages_missing_a_criterion(
96
+ per_unit: dict[str, dict[str, float]], unit_languages: dict[str, str]
97
+ ) -> tuple[tuple[str, tuple[str, ...]], ...]:
98
+ """Languages the analyzers did not measure on all three criteria.
99
+
100
+ One incomplete language disqualifies the whole reading, because the
101
+ value returned here replaces the built-in `declarations` pressure for
102
+ the entire repository — there is no seam to hand back a blend. Making
103
+ that seam is a larger change: the built-in path carries one aggregate
104
+ `declaration_band_pressure`, not a per-language one, so a genuine
105
+ per-language blend needs per-language aggregates plumbed through the
106
+ summary. Recorded rather than attempted here, because the honest
107
+ intermediate is to fall back for the repository when any language it
108
+ would speak for is incomplete, which is strictly more correct than
109
+ speaking for all of them on the strength of one.
110
+ """
111
+ return tuple(
112
+ (language, missing)
113
+ for language, concepts in sorted(
114
+ _concepts_by_language(per_unit, unit_languages).items())
115
+ if (missing := declaration_concepts_missing(concepts))
116
+ )
117
+
118
+
119
+ def complete_from_built_ins(
120
+ per_unit: dict[str, dict[str, float]],
121
+ unit_locations: dict[str, tuple[str, int]],
122
+ built_in: dict[tuple[str, int], dict[str, float]],
123
+ ) -> dict[str, dict[str, float]]:
124
+ """Fill criteria an analyzer did not measure from the built-in reading.
125
+
126
+ **Per concept, not per dimension.** The rule above asks whether a
127
+ language carries all three criteria; this is what lets a language
128
+ answer yes when no single tier measures all three. `lizard` reports
129
+ cyclomatic complexity and declaration lines for sixteen languages and
130
+ cognitive complexity for none; the built-in scanners report all three
131
+ for fourteen. Neither tier is complete alone, and the union of them,
132
+ per declaration, is.
133
+
134
+ Without this the analyzer tier can drive `declarations` for **Python
135
+ and nothing else** — measured, not inferred: of the 112 corpus
136
+ repositories, zero satisfied the criterion set without `complexipy`,
137
+ which reads Python only. PMD is the sole other wired tool that speaks
138
+ cognitive complexity, and it emits *violations above a threshold*
139
+ rather than a reading per declaration, so it can never supply a rate
140
+ over a population.
141
+
142
+ The join is `(path, start_line)`, and it is exact rather than
143
+ approximate: measured on this package, all 1,008 declarations lizard
144
+ reports match a built-in declaration at the same start line — 100% of
145
+ lizard's population, 92% of the built-in's, the difference being
146
+ declarations lizard does not count as functions.
147
+
148
+ Mixing tiers inside one unit is the point rather than a compromise.
149
+ The comparability the criterion set protects is that every
150
+ declaration was *eligible* to fail on all three, not that one
151
+ instrument measured all three; ADR 006 already makes the analyzer
152
+ primary for what it measures, and the bands both tiers are scored
153
+ through come from the same thresholds.
154
+ """
155
+ completed: dict[str, dict[str, float]] = {}
156
+ for unit, values in per_unit.items():
157
+ missing = declaration_concepts_missing(set(values))
158
+ if not missing:
159
+ completed[unit] = values
160
+ continue
161
+ location = unit_locations.get(unit)
162
+ fallback = built_in.get(location) if location else None
163
+ filled = dict(values)
164
+ for concept in missing:
165
+ if fallback and concept in fallback:
166
+ filled[concept] = fallback[concept]
167
+ if declaration_concepts_missing(set(filled)):
168
+ # No counterpart in the scanner's reading, so this
169
+ # declaration is not scoreable on the full criterion set by
170
+ # either tier — and it is dropped rather than left to
171
+ # disqualify its language.
172
+ #
173
+ # Measured, because the alternative looked reasonable and was
174
+ # not: `lizard` reads files the built-in scanner produces no
175
+ # declarations for — Alamofire's `docs/js/jquery.min.js` and
176
+ # `jazzy.js`, five generated documentation assets. Keeping
177
+ # them gave JavaScript a bucket that could never be completed
178
+ # and took the whole repository to the built-in tier, Swift
179
+ # and all. The same shape hid Java behind a repository's
180
+ # bundled `.js`.
181
+ #
182
+ # Dropping them also aligns the two populations: a unit the
183
+ # scanner never read is not in the built-in rate either, so
184
+ # including it in the analyzer rate compares a wider
185
+ # population against a narrower one.
186
+ continue
187
+ completed[unit] = filled
188
+ return completed
189
+
190
+
191
+ def built_in_readings(
192
+ metrics: object, root: str = ""
193
+ ) -> dict[tuple[str, int], dict[str, float]]:
194
+ """The scanner's three criteria per declaration, keyed for the join.
195
+
196
+ Lives beside the join rather than at the call site so both halves of
197
+ it share one path vocabulary. They did not, once, and the merge
198
+ shipped inert with the whole suite green.
199
+ """
200
+ return {
201
+ (canonical_path(root, metric.path), metric.start_line): {
202
+ "cyclomatic_complexity": float(metric.complexity),
203
+ "declaration_lines": float(metric.lines),
204
+ "cognitive_complexity": float(metric.cognitive),
205
+ }
206
+ for metric in metrics # type: ignore[attr-defined]
207
+ }
208
+
209
+
210
+ def canonical_path(root: str, path: str) -> str:
211
+ """One spelling for a file, so two tiers can be joined on it.
212
+
213
+ Three vocabularies meet at this join and no two agree: `lizard`
214
+ reports `./src/x.py`, `complexipy` reports the absolute path, and the
215
+ built-in scanner reports `src/x.py` relative to the scanned root.
216
+ Keying the join on any of them as given matches nothing, which is not
217
+ a hypothetical — it is how this merge shipped inert the first time,
218
+ with the whole suite green, because every test built both sides from
219
+ the same string.
220
+ """
221
+ text = path.replace("\\", "/")
222
+ if root and text.startswith(root):
223
+ text = text[len(root):]
224
+ return posixpath.normpath(text).lstrip("./") or text
225
+
226
+
227
+ def unit_locations(
228
+ measurements: list[Measurement], root: str = ""
229
+ ) -> dict[str, tuple[str, int]]:
230
+ """Where each unit starts, for the join above.
231
+
232
+ A measurement with no line cannot be joined and is simply absent
233
+ here: `complexipy` reports no start line, and needs none, because it
234
+ supplies the one concept the built-in tier would have been asked for.
235
+ """
236
+ located: dict[str, tuple[str, int]] = {}
237
+ for measurement in measurements:
238
+ path = measurement.path or ""
239
+ if path and measurement.line is not None:
240
+ located[measurement.unit] = (
241
+ canonical_path(root, path), int(measurement.line))
242
+ return located
243
+
244
+
245
+ def _scored_units(
246
+ per_unit: dict[str, dict[str, float]], unit_languages: dict[str, str]
247
+ ) -> dict[str, dict[str, float]] | None:
248
+ """The declarations the analyzer tier may score, or `None` for none.
249
+
250
+ **The one place eligibility is decided.** The rule it enforces is a
251
+ single sentence, and it is the sentence this bridge exists to keep
252
+ true: *every declaration contributing to the analyzer reading was
253
+ eligible to fail on all three criteria the built-in path fails on.*
254
+ A unit is eligible when its own language was measured on cyclomatic
255
+ complexity, declaration lines and cognitive complexity — not when
256
+ some other language in the repository was.
257
+
258
+ Returning the population rather than a verdict is the point. Four
259
+ earlier fixes to this bridge each corrected a formula and left the
260
+ eligibility question living somewhere else, and D137 was the fifth:
261
+ the check took a union across the repository while the units it
262
+ gated were scored one at a time, so a C repository holding one
263
+ `setup.py` had its C declarations scored on two criteria of three.
264
+ A caller cannot now score a set this function did not hand back, so
265
+ the two cannot be scoped differently again.
266
+
267
+ The verdict is all-or-nothing for the repository because the value
268
+ the caller produces replaces the built-in `declarations` pressure for
269
+ the whole of it; there is no seam to hand back a blend. Handing back
270
+ only the eligible units would trade a wrong-criteria reading for a
271
+ wrong-population one — a confident rate describing the 2% of a C
272
+ codebase that happens to be Python. A genuine per-language blend
273
+ needs per-language aggregates plumbed through the summary evidence,
274
+ which the built-in path does not carry today.
275
+ """
276
+ incomplete = _languages_missing_a_criterion(per_unit, unit_languages)
277
+ if incomplete:
278
+ return None
279
+ return per_unit
@@ -173,9 +173,19 @@ CATEGORY_ASPECTS: dict[str, dict[str, float]] = {
173
173
  ROOT_POPULATIONS: tuple[str, ...] = ("files_scanned", "declarations_scanned")
174
174
 
175
175
  POPULATION_FLOORS: dict[str, int] = {
176
- "files_scanned": 32,
176
+ # The 180-repo corpus minima (3.0.0), taken from `corpus.json`'s own
177
+ # `source_files` and `declarations` — the columns the floor test
178
+ # compares against — rather than from the measurement evidence,
179
+ # which counts a different population and gave 26 / 164.
180
+ #
181
+ # Were 32 / 139 / 36 against the 112-repo corpus. The added languages
182
+ # brought smaller repositories: SwiftyJSON and fd at 24 source files,
183
+ # express at 139 declarations. A floor above the corpus minimum is
184
+ # not a floor, it is a repository the scale cannot score while
185
+ # claiming to be calibrated by it.
186
+ "files_scanned": 24,
177
187
  "declarations_scanned": 139,
178
- "production_declarations_scanned": 36,
188
+ "production_declarations_scanned": 53,
179
189
  }
180
190
 
181
191
  # Which population each aspect's rate divides by. An aspect absent from