maintainability-agent 2.0.0__tar.gz → 2.2.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 (310) hide show
  1. {maintainability_agent-2.0.0/src/maintainability_agent.egg-info → maintainability_agent-2.2.0}/PKG-INFO +2 -2
  2. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/README.md +1 -1
  3. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/pyproject.toml +1 -1
  4. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0/src/maintainability_agent.egg-info}/PKG-INFO +2 -2
  5. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_agent.egg-info/SOURCES.txt +7 -0
  6. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/__init__.py +1 -1
  7. maintainability_agent-2.2.0/src/maintainability_audit/_conformance.py +183 -0
  8. maintainability_agent-2.2.0/src/maintainability_audit/_gates.py +122 -0
  9. maintainability_agent-2.2.0/src/maintainability_audit/_ratchet.py +109 -0
  10. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/cli.py +94 -23
  11. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/config.py +1 -1
  12. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/git_tools.py +38 -0
  13. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/instructions.py +57 -12
  14. maintainability_agent-2.2.0/tests/test_added_lines.py +123 -0
  15. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_anticipated_refusals.py +7 -0
  16. maintainability_agent-2.2.0/tests/test_dimension_ratchet.py +155 -0
  17. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_identity_resolution.py +5 -1
  18. maintainability_agent-2.2.0/tests/test_instruction_targets.py +154 -0
  19. maintainability_agent-2.2.0/tests/test_scope_conformance.py +301 -0
  20. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/LICENSE +0 -0
  21. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/setup.cfg +0 -0
  22. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_agent.egg-info/dependency_links.txt +0 -0
  23. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_agent.egg-info/entry_points.txt +0 -0
  24. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_agent.egg-info/requires.txt +0 -0
  25. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_agent.egg-info/top_level.txt +0 -0
  26. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/__main__.py +0 -0
  27. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_adapters.py +0 -0
  28. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_analysis.py +0 -0
  29. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_aspects.py +0 -0
  30. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_assets/analyzer-catalog.json +0 -0
  31. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_assets/standard.md +0 -0
  32. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_backfill.py +0 -0
  33. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_bands.py +0 -0
  34. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_built_ins.py +0 -0
  35. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_calibration.py +0 -0
  36. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_catalog.py +0 -0
  37. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_charts.py +0 -0
  38. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_cognitive.py +0 -0
  39. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_config_defaults.py +0 -0
  40. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_corroborate.py +0 -0
  41. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_coverage_notes.py +0 -0
  42. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_derive.py +0 -0
  43. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_discovery.py +0 -0
  44. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_documents.py +0 -0
  45. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_economics.py +0 -0
  46. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_economics_view.py +0 -0
  47. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_environment.py +0 -0
  48. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_evidence_view.py +0 -0
  49. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_finding_match.py +0 -0
  50. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_first_run.py +0 -0
  51. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_formula.py +0 -0
  52. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_generic.py +0 -0
  53. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_grant_ledger.py +0 -0
  54. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_history_view.py +0 -0
  55. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_hostile_prompt.py +0 -0
  56. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_hotspots.py +0 -0
  57. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_html_report_sections.py +0 -0
  58. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_html_view.py +0 -0
  59. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_identity.py +0 -0
  60. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_jvm_adapters.py +0 -0
  61. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_markdown_sections.py +0 -0
  62. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_masking.py +0 -0
  63. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_mcp_audit.py +0 -0
  64. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_mcp_gate.py +0 -0
  65. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_mcp_grants.py +0 -0
  66. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_mcp_refusals.py +0 -0
  67. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_mcp_resources.py +0 -0
  68. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_mcp_setup.py +0 -0
  69. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_metric_adapters.py +0 -0
  70. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_metrics_types.py +0 -0
  71. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_pillars.py +0 -0
  72. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_practice.py +0 -0
  73. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_pressures.py +0 -0
  74. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_ranges_c.py +0 -0
  75. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_ranges_core.py +0 -0
  76. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_ranges_cpp.py +0 -0
  77. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_ranges_csharp.py +0 -0
  78. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_ranges_fortran.py +0 -0
  79. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_ranges_java.py +0 -0
  80. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_ranges_js.py +0 -0
  81. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_recurrence.py +0 -0
  82. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_runner.py +0 -0
  83. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_safe_write.py +0 -0
  84. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_scan_history.py +0 -0
  85. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_scan_view.py +0 -0
  86. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_second_source.py +0 -0
  87. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_selection.py +0 -0
  88. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_semantic.py +0 -0
  89. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_semantic_policy.py +0 -0
  90. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_semantic_ts.py +0 -0
  91. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_semantic_view.py +0 -0
  92. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_setup_errors.py +0 -0
  93. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_setup_persist.py +0 -0
  94. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_skill_data/SKILL.md +0 -0
  95. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_skill_data/agents/anthropic.yaml +0 -0
  96. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_skill_data/agents/copilot.yaml +0 -0
  97. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_skill_data/agents/openai.yaml +0 -0
  98. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_skill_data/copilot/maintainability-agent.prompt.md +0 -0
  99. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_skill_data/references/finding-taxonomy.md +0 -0
  100. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_skill_install.py +0 -0
  101. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_stored_grants.py +0 -0
  102. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_tdd_view.py +0 -0
  103. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_test_execution.py +0 -0
  104. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_test_pairing.py +0 -0
  105. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_tokens.py +0 -0
  106. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_tool_adapters.py +0 -0
  107. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_trends.py +0 -0
  108. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_user_config.py +0 -0
  109. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_verdict_adapters.py +0 -0
  110. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_verification.py +0 -0
  111. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_work_order.py +0 -0
  112. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_work_order_view.py +0 -0
  113. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_work_order_weights.py +0 -0
  114. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/_xml.py +0 -0
  115. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/baseline.py +0 -0
  116. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/deadcode.py +0 -0
  117. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/declarations.py +0 -0
  118. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/duplication.py +0 -0
  119. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/evidence.py +0 -0
  120. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/history.py +0 -0
  121. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/idioms.py +0 -0
  122. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/mcp_server.py +0 -0
  123. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/metrics.py +0 -0
  124. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/prompts.py +0 -0
  125. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/renderers.py +0 -0
  126. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/report.py +0 -0
  127. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/sarif.py +0 -0
  128. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/scoring.py +0 -0
  129. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/similarity.py +0 -0
  130. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/src/maintainability_audit/source.py +0 -0
  131. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_acquisition_trust.py +0 -0
  132. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_adapter_docs.py +0 -0
  133. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_adapter_recordings.py +0 -0
  134. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_adapters.py +0 -0
  135. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analysis_coverage.py +0 -0
  136. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analysis_execution.py +0 -0
  137. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_bridge.py +0 -0
  138. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_config_isolation.py +0 -0
  139. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_estimate_claims.py +0 -0
  140. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_pinning.py +0 -0
  141. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_primary_estimate.py +0 -0
  142. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_provenance.py +0 -0
  143. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_provenance_exclusions.py +0 -0
  144. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_scoring_path.py +0 -0
  145. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_spread_range.py +0 -0
  146. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_analyzer_xml_bounds.py +0 -0
  147. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_annotations_match_behaviour.py +0 -0
  148. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_architecture.py +0 -0
  149. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_artifact_write_route_class.py +0 -0
  150. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_aspect_coverage.py +0 -0
  151. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_asset_snapshot_class.py +0 -0
  152. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_ast_reading_resolves_spellings.py +0 -0
  153. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_audit_components.py +0 -0
  154. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_authorization_freshness.py +0 -0
  155. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_authorship_gates.py +0 -0
  156. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_backfill.py +0 -0
  157. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_band_concealment_class.py +0 -0
  158. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_band_concealment_p3.py +0 -0
  159. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_band_pressures.py +0 -0
  160. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_bands.py +0 -0
  161. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_body_or_signature_class.py +0 -0
  162. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_c_declarations.py +0 -0
  163. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_calibration_corpus.py +0 -0
  164. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_calibration_drift.py +0 -0
  165. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_calibration_tooling.py +0 -0
  166. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_catalog_selection.py +0 -0
  167. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_catalog_tier_promise.py +0 -0
  168. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_chat_primary_docs.py +0 -0
  169. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_checkstyle_adapter.py +0 -0
  170. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_checkstyle_catalog.py +0 -0
  171. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_ci_history_recipe.py +0 -0
  172. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_ci_installs_the_analyzer_pool.py +0 -0
  173. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_claimed_languages.py +0 -0
  174. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_cli.py +0 -0
  175. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_clone_group_class.py +0 -0
  176. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_cognitive_complexity.py +0 -0
  177. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_cohort_statistics.py +0 -0
  178. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_concealment_properties.py +0 -0
  179. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_config_shape.py +0 -0
  180. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_consent_and_grant.py +0 -0
  181. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_consumer_migration.py +0 -0
  182. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_contract_claims.py +0 -0
  183. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_corroborate.py +0 -0
  184. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_coverage_by_language.py +0 -0
  185. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_cpp_declarations.py +0 -0
  186. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_csharp_declarations.py +0 -0
  187. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_d15_composition.py +0 -0
  188. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_d15_goal_directed.py +0 -0
  189. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_dead_code.py +0 -0
  190. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_declaration_edge_cases.py +0 -0
  191. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_declaration_grading.py +0 -0
  192. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_declaration_ranges.py +0 -0
  193. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_declaration_source.py +0 -0
  194. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_declared_authorship.py +0 -0
  195. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_declared_imports.py +0 -0
  196. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_derivation_mix.py +0 -0
  197. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_determinism.py +0 -0
  198. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_direct_runner_availability_class.py +0 -0
  199. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_discovery.py +0 -0
  200. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_doc_claims.py +0 -0
  201. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_docs_links.py +0 -0
  202. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_economic_context.py +0 -0
  203. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_empty_output_not_clean.py +0 -0
  204. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_empty_output_not_clean_adapters.py +0 -0
  205. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_environment_work_order.py +0 -0
  206. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_evidence_invariants.py +0 -0
  207. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_evidence_normalization.py +0 -0
  208. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_evidence_properties.py +0 -0
  209. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_exclusion_dialects.py +0 -0
  210. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_falsifier_standard.py +0 -0
  211. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_file_list_cap_class.py +0 -0
  212. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_finding_identity.py +0 -0
  213. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_first_run_elicitation.py +0 -0
  214. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_first_run_prompt.py +0 -0
  215. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_first_run_reelicitation.py +0 -0
  216. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_fix_breadth_window.py +0 -0
  217. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_fixed_form_fortran.py +0 -0
  218. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_format_ask.py +0 -0
  219. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_fortran_declarations.py +0 -0
  220. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_fortran_metrics.py +0 -0
  221. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_fortran_practice.py +0 -0
  222. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_git_argv.py +0 -0
  223. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_git_read_only.py +0 -0
  224. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_git_readonly_class.py +0 -0
  225. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_grant_only_user_tier.py +0 -0
  226. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_history.py +0 -0
  227. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_history_boundary.py +0 -0
  228. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_history_manifest.py +0 -0
  229. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_history_schema2.py +0 -0
  230. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_history_window.py +0 -0
  231. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_hostile_prompt.py +0 -0
  232. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_html_executive.py +0 -0
  233. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_html_section_parity_class.py +0 -0
  234. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_identity_docs.py +0 -0
  235. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_idiom_divergence.py +0 -0
  236. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_installed_wheel_runtime.py +0 -0
  237. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_java_range_guard.py +0 -0
  238. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_java_ranges.py +0 -0
  239. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_java_scored.py +0 -0
  240. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_java_wiring.py +0 -0
  241. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_js_complexity_operators.py +0 -0
  242. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_js_declarations.py +0 -0
  243. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_language_coverage.py +0 -0
  244. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_mcp_baseline_payload.py +0 -0
  245. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_mcp_history.py +0 -0
  246. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_mcp_server.py +0 -0
  247. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_migration_1_0.py +0 -0
  248. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_near_duplicates.py +0 -0
  249. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_network_disclosure.py +0 -0
  250. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_one_setup_across_surfaces_class.py +0 -0
  251. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_pattern_budget.py +0 -0
  252. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_phase6_claims.py +0 -0
  253. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_pillars_and_practice.py +0 -0
  254. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_platform_claim.py +0 -0
  255. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_pmd_adapter.py +0 -0
  256. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_pmd_audit_closures.py +0 -0
  257. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_pool_runs_by_config.py +0 -0
  258. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_population_floors.py +0 -0
  259. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_practice_nested_gates_class.py +0 -0
  260. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_presentation_refusals.py +0 -0
  261. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_promises.py +0 -0
  262. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_prompt_escalation_once.py +0 -0
  263. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_prompt_honesty.py +0 -0
  264. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_prompt_location_escalation_class.py +0 -0
  265. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_prove_falsifiers.py +0 -0
  266. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_python_declarations.py +0 -0
  267. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_read_boundary.py +0 -0
  268. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_readme_claims.py +0 -0
  269. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_recurrence.py +0 -0
  270. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_release_plan.py +0 -0
  271. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_release_plan_status.py +0 -0
  272. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_report_remedy_copy.py +0 -0
  273. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_roles_recorded.py +0 -0
  274. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_root_grants.py +0 -0
  275. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_rubric_ignores_project_lint_config.py +0 -0
  276. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_runner.py +0 -0
  277. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_sarif.py +0 -0
  278. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_scan_history.py +0 -0
  279. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_scan_scope.py +0 -0
  280. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_scanning.py +0 -0
  281. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_schema_fidelity.py +0 -0
  282. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_scoring_calibration.py +0 -0
  283. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_semantic_policy.py +0 -0
  284. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_setup_gate_completeness.py +0 -0
  285. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_setup_gate_independent_contract.py +0 -0
  286. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_setup_precondition.py +0 -0
  287. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_skill_install.py +0 -0
  288. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_source_index.py +0 -0
  289. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_spotbugs_adapter.py +0 -0
  290. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_spotbugs_catalog.py +0 -0
  291. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_stage8_contract.py +0 -0
  292. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_tdd_structure.py +0 -0
  293. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_test_execution_class.py +0 -0
  294. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_three_presentations.py +0 -0
  295. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_trends.py +0 -0
  296. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_type_fields_not_functions_class.py +0 -0
  297. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_uat_falsifiers.py +0 -0
  298. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_unread_code.py +0 -0
  299. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_user_config_tier.py +0 -0
  300. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_verification_audit_closures.py +0 -0
  301. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_verified_grade.py +0 -0
  302. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_version_claim.py +0 -0
  303. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_wheel_contents.py +0 -0
  304. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_work_order.py +0 -0
  305. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_work_order_items.py +0 -0
  306. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_work_order_selection.py +0 -0
  307. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_workflow_supply_chain.py +0 -0
  308. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_write_boundary.py +0 -0
  309. {maintainability_agent-2.0.0 → maintainability_agent-2.2.0}/tests/test_write_route_class.py +0 -0
  310. {maintainability_agent-2.0.0 → maintainability_agent-2.2.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.0.0
3
+ Version: 2.2.0
4
4
  Summary: Maintainability gate and AI remediation prompt generator for CI.
5
5
  Author: Marshall Guillory
6
6
  License: MIT
@@ -44,7 +44,7 @@ Dynamic: license-file
44
44
  **A deterministic, offline maintainability audit whose output is a _bounded
45
45
  work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
46
46
  says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
47
- Version **2.0.0**.
47
+ Version **2.2.0**.
48
48
 
49
49
  **Languages parsed:** Python, Java, C, C++, C#, Fortran (free-form *and*
50
50
  fixed-form), and the JS/TS family — each by a scanner written for it, and
@@ -5,7 +5,7 @@
5
5
  **A deterministic, offline maintainability audit whose output is a _bounded
6
6
  work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
7
7
  says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
8
- Version **2.0.0**.
8
+ Version **2.2.0**.
9
9
 
10
10
  **Languages parsed:** Python, Java, C, C++, C#, Fortran (free-form *and*
11
11
  fixed-form), and the JS/TS family — each by a scanner written for it, and
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "maintainability-agent"
7
- version = "2.0.0"
7
+ version = "2.2.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.0.0
3
+ Version: 2.2.0
4
4
  Summary: Maintainability gate and AI remediation prompt generator for CI.
5
5
  Author: Marshall Guillory
6
6
  License: MIT
@@ -44,7 +44,7 @@ Dynamic: license-file
44
44
  **A deterministic, offline maintainability audit whose output is a _bounded
45
45
  work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
46
46
  says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
47
- Version **2.0.0**.
47
+ Version **2.2.0**.
48
48
 
49
49
  **Languages parsed:** Python, Java, C, C++, C#, Fortran (free-form *and*
50
50
  fixed-form), and the JS/TS family — each by a scanner written for it, and
@@ -20,6 +20,7 @@ src/maintainability_audit/_catalog.py
20
20
  src/maintainability_audit/_charts.py
21
21
  src/maintainability_audit/_cognitive.py
22
22
  src/maintainability_audit/_config_defaults.py
23
+ src/maintainability_audit/_conformance.py
23
24
  src/maintainability_audit/_corroborate.py
24
25
  src/maintainability_audit/_coverage_notes.py
25
26
  src/maintainability_audit/_derive.py
@@ -32,6 +33,7 @@ src/maintainability_audit/_evidence_view.py
32
33
  src/maintainability_audit/_finding_match.py
33
34
  src/maintainability_audit/_first_run.py
34
35
  src/maintainability_audit/_formula.py
36
+ src/maintainability_audit/_gates.py
35
37
  src/maintainability_audit/_generic.py
36
38
  src/maintainability_audit/_grant_ledger.py
37
39
  src/maintainability_audit/_history_view.py
@@ -61,6 +63,7 @@ src/maintainability_audit/_ranges_csharp.py
61
63
  src/maintainability_audit/_ranges_fortran.py
62
64
  src/maintainability_audit/_ranges_java.py
63
65
  src/maintainability_audit/_ranges_js.py
66
+ src/maintainability_audit/_ratchet.py
64
67
  src/maintainability_audit/_recurrence.py
65
68
  src/maintainability_audit/_runner.py
66
69
  src/maintainability_audit/_safe_write.py
@@ -121,6 +124,7 @@ tests/test_acquisition_trust.py
121
124
  tests/test_adapter_docs.py
122
125
  tests/test_adapter_recordings.py
123
126
  tests/test_adapters.py
127
+ tests/test_added_lines.py
124
128
  tests/test_analysis_coverage.py
125
129
  tests/test_analysis_execution.py
126
130
  tests/test_analyzer_bridge.py
@@ -185,6 +189,7 @@ tests/test_declared_authorship.py
185
189
  tests/test_declared_imports.py
186
190
  tests/test_derivation_mix.py
187
191
  tests/test_determinism.py
192
+ tests/test_dimension_ratchet.py
188
193
  tests/test_direct_runner_availability_class.py
189
194
  tests/test_discovery.py
190
195
  tests/test_doc_claims.py
@@ -225,6 +230,7 @@ tests/test_identity_docs.py
225
230
  tests/test_identity_resolution.py
226
231
  tests/test_idiom_divergence.py
227
232
  tests/test_installed_wheel_runtime.py
233
+ tests/test_instruction_targets.py
228
234
  tests/test_java_range_guard.py
229
235
  tests/test_java_ranges.py
230
236
  tests/test_java_scored.py
@@ -270,6 +276,7 @@ tests/test_scan_history.py
270
276
  tests/test_scan_scope.py
271
277
  tests/test_scanning.py
272
278
  tests/test_schema_fidelity.py
279
+ tests/test_scope_conformance.py
273
280
  tests/test_scoring_calibration.py
274
281
  tests/test_semantic_policy.py
275
282
  tests/test_setup_gate_completeness.py
@@ -1,3 +1,3 @@
1
1
  """Maintainability Audit CI."""
2
2
 
3
- __version__ = "2.0.0"
3
+ __version__ = "2.2.0"
@@ -0,0 +1,183 @@
1
+ """Did the diff stay inside the work order it was given?
2
+
3
+ The product's central claim is a bounded work order: *fix exactly these
4
+ findings and refactor nothing else*. Until now that bound was an
5
+ **instruction**. The prompt said it, and nothing checked it — so an agent
6
+ that rewrote half the tree while closing one finding produced a diff
7
+ indistinguishable, to this tool, from one that did as it was told.
8
+
9
+ This is the check that makes the bound verifiable. It reads the finished
10
+ report and a revspec, and reports which changed files the work order
11
+ actually named. It is the first of the three mechanisms behind the
12
+ attestation artifact, and the one a code-generating agent cannot honestly
13
+ perform about itself.
14
+
15
+ **What it must not do is cry wolf.** A correct remediation almost always
16
+ touches a file the work order does not name: the test that proves the fix.
17
+ A check that flagged every good remediation would be turned off within a
18
+ week, so a test file is in scope when it pairs to a named path by the same
19
+ convention `_test_pairing` already uses for coverage — `test_foo.py` pairs
20
+ to `foo.py`. A test paired to nothing named is still reported, because
21
+ "while I was here I rewrote the suite" is exactly the drift being watched
22
+ for.
23
+
24
+ Three deliberate limits:
25
+
26
+ - **It never scores.** Whether a diff was obedient is a fact about an
27
+ agent's behaviour, not evidence about the code's condition, so nothing
28
+ here reaches scoring or moves a grade.
29
+ - **It reports, it does not gate**, unless a caller asks it to. A finding
30
+ can legitimately require touching a caller the work order did not name,
31
+ and a check that cannot be argued with becomes a check that is bypassed.
32
+ - **It compares paths, not intent.** A file being in scope says the work
33
+ order named it, not that the change to it was the right one. Review is
34
+ still review.
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ import re
40
+ from typing import Any
41
+
42
+ from ._metrics_types import is_test_path
43
+ from ._test_pairing import subject_stem
44
+
45
+
46
+ def _named_paths(work_order: list[dict[str, Any]]) -> set[str]:
47
+ """Every path the work order put in front of the agent."""
48
+ return {
49
+ str(item["path"]) for item in work_order
50
+ if isinstance(item, dict) and item.get("path")
51
+ }
52
+
53
+
54
+ def _pairs_to_named(path: str, named: set[str]) -> bool:
55
+ """Whether a test file covers something the work order named.
56
+
57
+ Uses the pairing convention the coverage aspect already applies, so a
58
+ remediation's own test is in scope without the caller declaring it —
59
+ and a test that covers nothing in the work order is not.
60
+ """
61
+ if not is_test_path(path):
62
+ return False
63
+ subject = subject_stem(path)
64
+ if not subject:
65
+ return False
66
+ return any(subject_stem(candidate) == subject for candidate in named)
67
+
68
+
69
+ #: Markers that switch a checker off rather than satisfy it. Per language,
70
+ #: because the vocabulary differs and a single regex over all of them
71
+ #: matches prose: "# type: ignore" in a docstring explaining the convention
72
+ #: is not a suppression, and neither is this comment.
73
+ #:
74
+ #: Deliberately narrow. A marker here has to be a real directive that a
75
+ #: real tool obeys; guessing wider would report ordinary comments as
76
+ #: evasion, and a check that cries wolf is a check that gets switched off —
77
+ #: the same reasoning as the test-pairing rule above.
78
+ SUPPRESSION_MARKERS: tuple[tuple[str, str], ...] = (
79
+ (r"#\s*noqa\b", "noqa"),
80
+ (r"#\s*type:\s*ignore\b", "type: ignore"),
81
+ (r"#\s*pragma:\s*no\s*cover\b", "pragma: no cover"),
82
+ (r"#\s*nosec\b", "nosec"),
83
+ (r"//\s*eslint-disable", "eslint-disable"),
84
+ (r"/\*\s*eslint-disable", "eslint-disable"),
85
+ (r"//\s*@ts-(ignore|expect-error)\b", "ts-ignore"),
86
+ (r"@SuppressWarnings\b", "SuppressWarnings"),
87
+ (r"//\s*NOSONAR\b", "NOSONAR"),
88
+ (r"#\s*pylint:\s*disable\b", "pylint: disable"),
89
+ (r"@pytest\.mark\.(skip|xfail)\b", "skipped test"),
90
+ (r"@unittest\.skip\b", "skipped test"),
91
+ (r"\bit\.skip\(|\bdescribe\.skip\(", "skipped test"),
92
+ (r"@Disabled\b", "disabled test"),
93
+ (r"@Ignore\b", "ignored test"),
94
+ )
95
+
96
+ _SUPPRESSION = tuple(
97
+ (re.compile(pattern, re.IGNORECASE), label) for pattern, label in SUPPRESSION_MARKERS
98
+ )
99
+
100
+
101
+ def suppressions_added(
102
+ added: dict[str, list[tuple[int, str]]], named: set[str]
103
+ ) -> list[dict[str, Any]]:
104
+ """Suppressions this change introduced, and whether they land on a finding.
105
+
106
+ The failure being watched for: a finding is closed by making it
107
+ invisible rather than by fixing it. Add `# noqa` to the flagged line and
108
+ the next audit reports nothing — the scan goes green over code nobody
109
+ repaired, and scope conformance alone would call that diff obedient,
110
+ because the file it touched is exactly the file the work order named.
111
+
112
+ Only *added* lines are read. A suppression already in the tree is not
113
+ evidence about this change, and treating a decade of accumulated
114
+ directives as newly written would drown the signal that matters.
115
+
116
+ `on_named_path` is that signal: a suppression added to a file the work
117
+ order flagged is the shape of a finding being silenced, while one added
118
+ elsewhere is ordinary engineering that a reviewer may still want to see.
119
+ """
120
+ found: list[dict[str, Any]] = []
121
+ for path in sorted(added):
122
+ for line_number, text in added[path]:
123
+ for pattern, label in _SUPPRESSION:
124
+ if pattern.search(text):
125
+ found.append({
126
+ "path": path,
127
+ "line": line_number,
128
+ "marker": label,
129
+ "on_named_path": path in named,
130
+ })
131
+ break
132
+ return found
133
+
134
+
135
+ def scope_conformance(
136
+ report: dict[str, Any],
137
+ changed: set[str],
138
+ revspec: str,
139
+ added: dict[str, list[tuple[int, str]]] | None = None,
140
+ ) -> dict[str, Any]:
141
+ """How a diff relates to the work order the agent was handed.
142
+
143
+ Returns the record, never a verdict on the code. `in_scope`,
144
+ `paired_tests` and `out_of_scope` partition the changed files;
145
+ `unaddressed` names work-order paths the diff did not touch, which is
146
+ not a failure — a bounded change may take one item at a time.
147
+ """
148
+ work_order = report.get("work_order") or []
149
+ named = _named_paths(work_order)
150
+
151
+ in_scope = sorted(path for path in changed if path in named)
152
+ remaining = {path for path in changed if path not in named}
153
+ paired = sorted(path for path in remaining if _pairs_to_named(path, named))
154
+ out_of_scope = sorted(remaining - set(paired))
155
+ suppressions = suppressions_added(added or {}, named)
156
+ silenced = [item for item in suppressions if item["on_named_path"]]
157
+
158
+ return {
159
+ "revspec": revspec,
160
+ "work_order_items": len(work_order),
161
+ "named_paths": sorted(named),
162
+ "changed_paths": sorted(changed),
163
+ "in_scope": in_scope,
164
+ "paired_tests": paired,
165
+ "out_of_scope": out_of_scope,
166
+ "unaddressed": sorted(named - set(in_scope)),
167
+ "suppressions_added": suppressions,
168
+ "suppressions_on_named_paths": len(silenced),
169
+ # Two questions, kept apart. `conformant` answers "did it change
170
+ # only what it was asked to"; `clean` also answers "and without
171
+ # switching a checker off in a file that was flagged". Scope alone
172
+ # is satisfiable by adding `# noqa` to the named file and changing
173
+ # nothing else, which is the evasion this pairing exists to catch.
174
+ "conformant": not out_of_scope,
175
+ "clean": not out_of_scope and not silenced,
176
+ # Stated so a reader does not infer more than was checked. The
177
+ # record says which files the work order named, not whether the
178
+ # edits inside them were the right ones.
179
+ "note": (
180
+ "Paths and added lines. A file in scope means the work order "
181
+ "named it, not that the change to it was correct."
182
+ ),
183
+ }
@@ -0,0 +1,122 @@
1
+ """What happens after the audit and before the exit code.
2
+
3
+ Split from `cli.py` when it crossed this project's 500-line file gate —
4
+ the same reason `_mcp_setup`, `_adapters` and `test_architecture` were
5
+ split before it.
6
+
7
+ The split is not arbitrary. Everything here composes from the **finished
8
+ report**: whether the diff obeyed its work order, whether a dimension
9
+ slipped since the last comparable scan, and what those two facts mean for
10
+ the process exit code. None of it measures a tree and none of it may reach
11
+ scoring — a fact about an agent's behaviour, or about the difference
12
+ between two scans, is not evidence about the code's condition.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ from .baseline import findings_not_in_baseline
22
+ from .git_tools import added_lines, changed_paths
23
+
24
+
25
+ def attach_conformance(args: argparse.Namespace, report: dict) -> None:
26
+ """Record how the diff relates to the work order, when asked.
27
+
28
+ Assembly, not scoring: the record is attached to the report and reaches
29
+ no dimension, because whether a diff was obedient is a fact about an
30
+ agent's behaviour and not evidence about the code's condition.
31
+ """
32
+ if not args.conformance:
33
+ return
34
+ from ._conformance import scope_conformance
35
+
36
+ root = Path(report["root"])
37
+ changed = changed_paths(root, args.conformance)
38
+ added = added_lines(root, args.conformance)
39
+ report["scope_conformance"] = scope_conformance(
40
+ report, changed, args.conformance, added
41
+ )
42
+
43
+
44
+ def attach_ratchet(report: dict, history_path: Path) -> None:
45
+ """Record whether any category slipped since the last comparable scan."""
46
+ from ._ratchet import dimension_ratchet
47
+ from ._scan_history import read_history
48
+
49
+ if not history_path.exists():
50
+ return
51
+ report["dimension_ratchet"] = dimension_ratchet(read_history(history_path))
52
+
53
+
54
+ def _attach_post_audit_records(
55
+ args: argparse.Namespace, report: dict, history_path: Path
56
+ ) -> None:
57
+ """Records composed from the finished report, before it is rendered.
58
+
59
+ Both read the report rather than the tree, and neither reaches scoring:
60
+ whether a diff obeyed its work order, and whether a dimension slipped
61
+ since the last comparable scan, are facts about a change and its
62
+ history — not evidence about the code's condition.
63
+ """
64
+ attach_conformance(args, report)
65
+ attach_ratchet(report, history_path)
66
+
67
+
68
+ def _conformance_exit(args: argparse.Namespace, report: dict) -> int:
69
+ """0 to continue; otherwise the exit code the conformance gate demands."""
70
+ if not args.fail_on_out_of_scope:
71
+ return 0
72
+ record = report.get("scope_conformance")
73
+ if record is None:
74
+ print("--fail-on-out-of-scope needs --conformance REVSPEC", file=sys.stderr)
75
+ return 2
76
+ # `clean`, not `conformant`: staying in scope while silencing the finding
77
+ # is the evasion the pairing exists to catch, and a gate that accepted it
78
+ # would be satisfied by the thing it guards against.
79
+ return 0 if record["clean"] else 1
80
+
81
+
82
+ def _regression_exit(args: argparse.Namespace, report: dict) -> int:
83
+ """0 to continue; otherwise the exit code the ratchet demands."""
84
+ if not args.fail_on_regression:
85
+ return 0
86
+ ratchet = report.get("dimension_ratchet")
87
+ if ratchet is None:
88
+ print("--fail-on-regression needs a recorded history (--record-history)",
89
+ file=sys.stderr)
90
+ return 2
91
+ # A run that could not compare does not pass quietly: exiting 0 where the
92
+ # question was never asked is the "absence read as a pass" shape.
93
+ #
94
+ # The reason is not interpolated — it derives from recorded scan history,
95
+ # and code scanning is right that history fields have no business in a log
96
+ # line. It stays in the report, which a caller reads deliberately.
97
+ # Suppressing that alert was refused: this release ships the check that
98
+ # treats a suppression as a finding rather than a fix.
99
+ if not ratchet["comparable"]:
100
+ print("no comparable scan for --fail-on-regression; see "
101
+ "dimension_ratchet.reason in the report", file=sys.stderr)
102
+ return 2
103
+ return 1 if ratchet["regressed"] else 0
104
+
105
+
106
+ def audit_exit_code(args: argparse.Namespace, report: dict) -> int:
107
+ if args.fail_on_new:
108
+ # Structured matching, never a label-set difference: a label
109
+ # changes under `git mv` and same-name reorder, and failing a
110
+ # build over either is a false report about the change.
111
+ root = Path(report["root"])
112
+ if findings_not_in_baseline(report, args.baseline, root):
113
+ return 1
114
+ if args.fail_on_gate and report["hard_gate_failures"]:
115
+ return 1
116
+ conformance = _conformance_exit(args, report)
117
+ if conformance:
118
+ return conformance
119
+ regression = _regression_exit(args, report)
120
+ if regression:
121
+ return regression
122
+ return 0
@@ -0,0 +1,109 @@
1
+ """Did any dimension get worse than the last comparable scan?
2
+
3
+ The third mechanism behind the attestation. `--fail-on-new` already catches
4
+ a finding that clears and returns, using structured identity so a rename is
5
+ not a new finding. What nothing caught is the slower failure: a change that
6
+ improves one dimension while quietly regressing another. Both scans are
7
+ green by every existing gate, the overall estimate can even rise, and the
8
+ thing that got worse is never named.
9
+
10
+ **The comparison is only honest inside one segment.** History is already
11
+ split wherever the instrument changed — a different calibration constant, a
12
+ different analyzer population — because two scans taken with different
13
+ instruments cannot be subtracted. `_scan_history.segments` owns that rule,
14
+ and this module refuses rather than reaching across a break. A ratchet that
15
+ compared a 1.x score to a 2.0 score would have reported every repository on
16
+ earth as catastrophically regressed on the day the corpus was extended, and
17
+ it would have been the ratchet that was wrong.
18
+
19
+ So there are three outcomes, not two: **held**, **regressed**, and **not
20
+ comparable**. The third is not a pass. It says the question could not be
21
+ asked, which is what a reader needs in order to know they are not being
22
+ reassured by silence.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ from typing import Any
28
+
29
+ from ._scan_history import ScanRecord, segments
30
+
31
+ #: Scores are reported to one decimal, so a difference below this is the
32
+ #: rounding boundary rather than a movement. Named rather than inlined
33
+ #: because it is a judgment: too tight and every scan trips on noise, too
34
+ #: loose and a real slide hides under the threshold.
35
+ TOLERANCE = 0.05
36
+
37
+
38
+ def _series(record: ScanRecord, field: str) -> dict[str, float]:
39
+ values = getattr(record, field, None) or {}
40
+ return {
41
+ name: float(value) for name, value in values.items()
42
+ if isinstance(value, (int, float))
43
+ }
44
+
45
+
46
+ def _regressions(
47
+ previous: ScanRecord, current: ScanRecord, field: str
48
+ ) -> list[dict[str, Any]]:
49
+ """Names that dropped, with the size of the drop."""
50
+ before, after = _series(previous, field), _series(current, field)
51
+ found = []
52
+ for name, was in sorted(before.items()):
53
+ # A name the current scan does not carry is not a regression: the
54
+ # aspect became unmeasurable, which the evidence model already
55
+ # reports as `Unknown` rather than as a zero. Scoring it as a drop
56
+ # to nothing would turn withheld evidence into a failure, which is
57
+ # the exact inversion P3 forbids.
58
+ if name not in after:
59
+ continue
60
+ now = after[name]
61
+ if was - now > TOLERANCE:
62
+ found.append({
63
+ "name": name, "was": round(was, 2),
64
+ "now": round(now, 2), "drop": round(was - now, 2),
65
+ })
66
+ return found
67
+
68
+
69
+ def dimension_ratchet(records: list[ScanRecord]) -> dict[str, Any]:
70
+ """Compare the newest scan with the previous comparable one.
71
+
72
+ `comparable` is the load-bearing field. False means the two scans sit
73
+ either side of an instrument change, or there is no earlier scan at
74
+ all — in both cases the question was not asked, and a caller must not
75
+ read that as "nothing regressed".
76
+ """
77
+ if len(records) < 2:
78
+ return {
79
+ "comparable": False,
80
+ "reason": "no earlier scan to compare against",
81
+ "regressed": [],
82
+ }
83
+
84
+ latest = segments(records)[-1]
85
+ if len(latest.records) < 2:
86
+ return {
87
+ "comparable": False,
88
+ "reason": (
89
+ latest.break_reason
90
+ or "the previous scan was taken with a different instrument"
91
+ ),
92
+ "regressed": [],
93
+ }
94
+
95
+ previous, current = latest.records[-2], latest.records[-1]
96
+ categories = _regressions(previous, current, "categories")
97
+ aspects = _regressions(previous, current, "aspects")
98
+
99
+ return {
100
+ "comparable": True,
101
+ "against": getattr(previous, "commit", None),
102
+ "categories": categories,
103
+ "aspects": aspects,
104
+ # Categories only. An aspect can move while the category holding it
105
+ # holds, and failing on every aspect wobble makes the gate noise.
106
+ # Aspects are reported so a reader can see what moved underneath.
107
+ "regressed": categories,
108
+ "held": not categories,
109
+ }