maintainability-agent 2.0.0__tar.gz → 2.1.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 (307) hide show
  1. {maintainability_agent-2.0.0/src/maintainability_agent.egg-info → maintainability_agent-2.1.0}/PKG-INFO +2 -2
  2. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/README.md +1 -1
  3. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/pyproject.toml +1 -1
  4. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0/src/maintainability_agent.egg-info}/PKG-INFO +2 -2
  5. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_agent.egg-info/SOURCES.txt +4 -0
  6. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/__init__.py +1 -1
  7. maintainability_agent-2.1.0/src/maintainability_audit/_conformance.py +183 -0
  8. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/cli.py +113 -10
  9. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/config.py +1 -1
  10. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/git_tools.py +38 -0
  11. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/instructions.py +57 -12
  12. maintainability_agent-2.1.0/tests/test_added_lines.py +123 -0
  13. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_anticipated_refusals.py +7 -0
  14. maintainability_agent-2.1.0/tests/test_instruction_targets.py +154 -0
  15. maintainability_agent-2.1.0/tests/test_scope_conformance.py +299 -0
  16. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/LICENSE +0 -0
  17. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/setup.cfg +0 -0
  18. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_agent.egg-info/dependency_links.txt +0 -0
  19. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_agent.egg-info/entry_points.txt +0 -0
  20. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_agent.egg-info/requires.txt +0 -0
  21. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_agent.egg-info/top_level.txt +0 -0
  22. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/__main__.py +0 -0
  23. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_adapters.py +0 -0
  24. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_analysis.py +0 -0
  25. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_aspects.py +0 -0
  26. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_assets/analyzer-catalog.json +0 -0
  27. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_assets/standard.md +0 -0
  28. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_backfill.py +0 -0
  29. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_bands.py +0 -0
  30. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_built_ins.py +0 -0
  31. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_calibration.py +0 -0
  32. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_catalog.py +0 -0
  33. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_charts.py +0 -0
  34. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_cognitive.py +0 -0
  35. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_config_defaults.py +0 -0
  36. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_corroborate.py +0 -0
  37. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_coverage_notes.py +0 -0
  38. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_derive.py +0 -0
  39. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_discovery.py +0 -0
  40. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_documents.py +0 -0
  41. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_economics.py +0 -0
  42. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_economics_view.py +0 -0
  43. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_environment.py +0 -0
  44. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_evidence_view.py +0 -0
  45. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_finding_match.py +0 -0
  46. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_first_run.py +0 -0
  47. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_formula.py +0 -0
  48. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_generic.py +0 -0
  49. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_grant_ledger.py +0 -0
  50. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_history_view.py +0 -0
  51. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_hostile_prompt.py +0 -0
  52. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_hotspots.py +0 -0
  53. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_html_report_sections.py +0 -0
  54. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_html_view.py +0 -0
  55. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_identity.py +0 -0
  56. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_jvm_adapters.py +0 -0
  57. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_markdown_sections.py +0 -0
  58. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_masking.py +0 -0
  59. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_mcp_audit.py +0 -0
  60. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_mcp_gate.py +0 -0
  61. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_mcp_grants.py +0 -0
  62. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_mcp_refusals.py +0 -0
  63. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_mcp_resources.py +0 -0
  64. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_mcp_setup.py +0 -0
  65. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_metric_adapters.py +0 -0
  66. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_metrics_types.py +0 -0
  67. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_pillars.py +0 -0
  68. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_practice.py +0 -0
  69. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_pressures.py +0 -0
  70. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_ranges_c.py +0 -0
  71. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_ranges_core.py +0 -0
  72. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_ranges_cpp.py +0 -0
  73. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_ranges_csharp.py +0 -0
  74. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_ranges_fortran.py +0 -0
  75. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_ranges_java.py +0 -0
  76. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_ranges_js.py +0 -0
  77. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_recurrence.py +0 -0
  78. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_runner.py +0 -0
  79. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_safe_write.py +0 -0
  80. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_scan_history.py +0 -0
  81. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_scan_view.py +0 -0
  82. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_second_source.py +0 -0
  83. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_selection.py +0 -0
  84. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_semantic.py +0 -0
  85. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_semantic_policy.py +0 -0
  86. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_semantic_ts.py +0 -0
  87. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_semantic_view.py +0 -0
  88. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_setup_errors.py +0 -0
  89. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_setup_persist.py +0 -0
  90. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_skill_data/SKILL.md +0 -0
  91. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_skill_data/agents/anthropic.yaml +0 -0
  92. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_skill_data/agents/copilot.yaml +0 -0
  93. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_skill_data/agents/openai.yaml +0 -0
  94. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_skill_data/copilot/maintainability-agent.prompt.md +0 -0
  95. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_skill_data/references/finding-taxonomy.md +0 -0
  96. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_skill_install.py +0 -0
  97. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_stored_grants.py +0 -0
  98. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_tdd_view.py +0 -0
  99. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_test_execution.py +0 -0
  100. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_test_pairing.py +0 -0
  101. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_tokens.py +0 -0
  102. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_tool_adapters.py +0 -0
  103. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_trends.py +0 -0
  104. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_user_config.py +0 -0
  105. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_verdict_adapters.py +0 -0
  106. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_verification.py +0 -0
  107. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_work_order.py +0 -0
  108. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_work_order_view.py +0 -0
  109. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_work_order_weights.py +0 -0
  110. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/_xml.py +0 -0
  111. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/baseline.py +0 -0
  112. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/deadcode.py +0 -0
  113. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/declarations.py +0 -0
  114. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/duplication.py +0 -0
  115. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/evidence.py +0 -0
  116. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/history.py +0 -0
  117. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/idioms.py +0 -0
  118. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/mcp_server.py +0 -0
  119. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/metrics.py +0 -0
  120. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/prompts.py +0 -0
  121. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/renderers.py +0 -0
  122. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/report.py +0 -0
  123. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/sarif.py +0 -0
  124. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/scoring.py +0 -0
  125. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/similarity.py +0 -0
  126. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/src/maintainability_audit/source.py +0 -0
  127. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_acquisition_trust.py +0 -0
  128. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_adapter_docs.py +0 -0
  129. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_adapter_recordings.py +0 -0
  130. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_adapters.py +0 -0
  131. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_analysis_coverage.py +0 -0
  132. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_analysis_execution.py +0 -0
  133. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_analyzer_bridge.py +0 -0
  134. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_analyzer_config_isolation.py +0 -0
  135. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_analyzer_estimate_claims.py +0 -0
  136. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_analyzer_pinning.py +0 -0
  137. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_analyzer_primary_estimate.py +0 -0
  138. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_analyzer_provenance.py +0 -0
  139. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_analyzer_provenance_exclusions.py +0 -0
  140. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_analyzer_scoring_path.py +0 -0
  141. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_analyzer_spread_range.py +0 -0
  142. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_analyzer_xml_bounds.py +0 -0
  143. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_annotations_match_behaviour.py +0 -0
  144. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_architecture.py +0 -0
  145. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_artifact_write_route_class.py +0 -0
  146. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_aspect_coverage.py +0 -0
  147. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_asset_snapshot_class.py +0 -0
  148. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_ast_reading_resolves_spellings.py +0 -0
  149. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_audit_components.py +0 -0
  150. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_authorization_freshness.py +0 -0
  151. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_authorship_gates.py +0 -0
  152. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_backfill.py +0 -0
  153. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_band_concealment_class.py +0 -0
  154. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_band_concealment_p3.py +0 -0
  155. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_band_pressures.py +0 -0
  156. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_bands.py +0 -0
  157. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_body_or_signature_class.py +0 -0
  158. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_c_declarations.py +0 -0
  159. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_calibration_corpus.py +0 -0
  160. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_calibration_drift.py +0 -0
  161. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_calibration_tooling.py +0 -0
  162. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_catalog_selection.py +0 -0
  163. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_catalog_tier_promise.py +0 -0
  164. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_chat_primary_docs.py +0 -0
  165. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_checkstyle_adapter.py +0 -0
  166. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_checkstyle_catalog.py +0 -0
  167. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_ci_history_recipe.py +0 -0
  168. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_ci_installs_the_analyzer_pool.py +0 -0
  169. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_claimed_languages.py +0 -0
  170. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_cli.py +0 -0
  171. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_clone_group_class.py +0 -0
  172. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_cognitive_complexity.py +0 -0
  173. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_cohort_statistics.py +0 -0
  174. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_concealment_properties.py +0 -0
  175. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_config_shape.py +0 -0
  176. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_consent_and_grant.py +0 -0
  177. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_consumer_migration.py +0 -0
  178. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_contract_claims.py +0 -0
  179. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_corroborate.py +0 -0
  180. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_coverage_by_language.py +0 -0
  181. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_cpp_declarations.py +0 -0
  182. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_csharp_declarations.py +0 -0
  183. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_d15_composition.py +0 -0
  184. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_d15_goal_directed.py +0 -0
  185. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_dead_code.py +0 -0
  186. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_declaration_edge_cases.py +0 -0
  187. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_declaration_grading.py +0 -0
  188. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_declaration_ranges.py +0 -0
  189. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_declaration_source.py +0 -0
  190. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_declared_authorship.py +0 -0
  191. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_declared_imports.py +0 -0
  192. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_derivation_mix.py +0 -0
  193. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_determinism.py +0 -0
  194. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_direct_runner_availability_class.py +0 -0
  195. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_discovery.py +0 -0
  196. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_doc_claims.py +0 -0
  197. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_docs_links.py +0 -0
  198. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_economic_context.py +0 -0
  199. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_empty_output_not_clean.py +0 -0
  200. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_empty_output_not_clean_adapters.py +0 -0
  201. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_environment_work_order.py +0 -0
  202. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_evidence_invariants.py +0 -0
  203. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_evidence_normalization.py +0 -0
  204. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_evidence_properties.py +0 -0
  205. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_exclusion_dialects.py +0 -0
  206. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_falsifier_standard.py +0 -0
  207. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_file_list_cap_class.py +0 -0
  208. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_finding_identity.py +0 -0
  209. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_first_run_elicitation.py +0 -0
  210. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_first_run_prompt.py +0 -0
  211. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_first_run_reelicitation.py +0 -0
  212. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_fix_breadth_window.py +0 -0
  213. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_fixed_form_fortran.py +0 -0
  214. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_format_ask.py +0 -0
  215. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_fortran_declarations.py +0 -0
  216. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_fortran_metrics.py +0 -0
  217. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_fortran_practice.py +0 -0
  218. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_git_argv.py +0 -0
  219. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_git_read_only.py +0 -0
  220. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_git_readonly_class.py +0 -0
  221. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_grant_only_user_tier.py +0 -0
  222. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_history.py +0 -0
  223. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_history_boundary.py +0 -0
  224. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_history_manifest.py +0 -0
  225. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_history_schema2.py +0 -0
  226. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_history_window.py +0 -0
  227. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_hostile_prompt.py +0 -0
  228. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_html_executive.py +0 -0
  229. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_html_section_parity_class.py +0 -0
  230. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_identity_docs.py +0 -0
  231. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_identity_resolution.py +0 -0
  232. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_idiom_divergence.py +0 -0
  233. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_installed_wheel_runtime.py +0 -0
  234. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_java_range_guard.py +0 -0
  235. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_java_ranges.py +0 -0
  236. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_java_scored.py +0 -0
  237. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_java_wiring.py +0 -0
  238. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_js_complexity_operators.py +0 -0
  239. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_js_declarations.py +0 -0
  240. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_language_coverage.py +0 -0
  241. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_mcp_baseline_payload.py +0 -0
  242. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_mcp_history.py +0 -0
  243. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_mcp_server.py +0 -0
  244. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_migration_1_0.py +0 -0
  245. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_near_duplicates.py +0 -0
  246. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_network_disclosure.py +0 -0
  247. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_one_setup_across_surfaces_class.py +0 -0
  248. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_pattern_budget.py +0 -0
  249. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_phase6_claims.py +0 -0
  250. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_pillars_and_practice.py +0 -0
  251. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_platform_claim.py +0 -0
  252. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_pmd_adapter.py +0 -0
  253. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_pmd_audit_closures.py +0 -0
  254. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_pool_runs_by_config.py +0 -0
  255. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_population_floors.py +0 -0
  256. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_practice_nested_gates_class.py +0 -0
  257. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_presentation_refusals.py +0 -0
  258. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_promises.py +0 -0
  259. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_prompt_escalation_once.py +0 -0
  260. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_prompt_honesty.py +0 -0
  261. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_prompt_location_escalation_class.py +0 -0
  262. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_prove_falsifiers.py +0 -0
  263. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_python_declarations.py +0 -0
  264. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_read_boundary.py +0 -0
  265. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_readme_claims.py +0 -0
  266. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_recurrence.py +0 -0
  267. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_release_plan.py +0 -0
  268. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_release_plan_status.py +0 -0
  269. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_report_remedy_copy.py +0 -0
  270. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_roles_recorded.py +0 -0
  271. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_root_grants.py +0 -0
  272. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_rubric_ignores_project_lint_config.py +0 -0
  273. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_runner.py +0 -0
  274. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_sarif.py +0 -0
  275. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_scan_history.py +0 -0
  276. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_scan_scope.py +0 -0
  277. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_scanning.py +0 -0
  278. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_schema_fidelity.py +0 -0
  279. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_scoring_calibration.py +0 -0
  280. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_semantic_policy.py +0 -0
  281. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_setup_gate_completeness.py +0 -0
  282. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_setup_gate_independent_contract.py +0 -0
  283. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_setup_precondition.py +0 -0
  284. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_skill_install.py +0 -0
  285. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_source_index.py +0 -0
  286. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_spotbugs_adapter.py +0 -0
  287. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_spotbugs_catalog.py +0 -0
  288. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_stage8_contract.py +0 -0
  289. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_tdd_structure.py +0 -0
  290. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_test_execution_class.py +0 -0
  291. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_three_presentations.py +0 -0
  292. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_trends.py +0 -0
  293. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_type_fields_not_functions_class.py +0 -0
  294. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_uat_falsifiers.py +0 -0
  295. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_unread_code.py +0 -0
  296. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_user_config_tier.py +0 -0
  297. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_verification_audit_closures.py +0 -0
  298. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_verified_grade.py +0 -0
  299. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_version_claim.py +0 -0
  300. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_wheel_contents.py +0 -0
  301. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_work_order.py +0 -0
  302. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_work_order_items.py +0 -0
  303. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_work_order_selection.py +0 -0
  304. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_workflow_supply_chain.py +0 -0
  305. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_write_boundary.py +0 -0
  306. {maintainability_agent-2.0.0 → maintainability_agent-2.1.0}/tests/test_write_route_class.py +0 -0
  307. {maintainability_agent-2.0.0 → maintainability_agent-2.1.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.1.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.1.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.1.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.1.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.1.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.1.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
@@ -121,6 +122,7 @@ tests/test_acquisition_trust.py
121
122
  tests/test_adapter_docs.py
122
123
  tests/test_adapter_recordings.py
123
124
  tests/test_adapters.py
125
+ tests/test_added_lines.py
124
126
  tests/test_analysis_coverage.py
125
127
  tests/test_analysis_execution.py
126
128
  tests/test_analyzer_bridge.py
@@ -225,6 +227,7 @@ tests/test_identity_docs.py
225
227
  tests/test_identity_resolution.py
226
228
  tests/test_idiom_divergence.py
227
229
  tests/test_installed_wheel_runtime.py
230
+ tests/test_instruction_targets.py
228
231
  tests/test_java_range_guard.py
229
232
  tests/test_java_ranges.py
230
233
  tests/test_java_scored.py
@@ -270,6 +273,7 @@ tests/test_scan_history.py
270
273
  tests/test_scan_scope.py
271
274
  tests/test_scanning.py
272
275
  tests/test_schema_fidelity.py
276
+ tests/test_scope_conformance.py
273
277
  tests/test_scoring_calibration.py
274
278
  tests/test_semantic_policy.py
275
279
  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.1.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
+ }
@@ -36,14 +36,43 @@ from .config import (
36
36
  load_config,
37
37
  repository_path,
38
38
  )
39
- from .git_tools import changed_paths
40
- from .instructions import instruction_path_for_target, write_instruction_pack
39
+ from .git_tools import added_lines, changed_paths
40
+ from .instructions import (
41
+ INSTRUCTION_TARGETS,
42
+ UnknownTarget,
43
+ instruction_path_for_target,
44
+ write_instruction_pack,
45
+ )
41
46
  from .prompts import render_agent_instructions, render_ai_prompt, render_hostile_audit_prompt
42
47
  from .renderers import render_html, render_markdown, render_pr_comment
43
48
  from .report import build_report
44
49
  from .sarif import read_sarif_inputs, report_to_sarif
45
50
 
46
51
 
52
+ def _add_gate_arguments(parser: argparse.ArgumentParser) -> None:
53
+ """What can make a run fail, and the conformance record behind one.
54
+
55
+ Split from `add_arguments` when the conformance flags took it past this
56
+ project's own function-length gate — which is the gate doing its job on
57
+ the change that added a gate.
58
+ """
59
+ parser.add_argument("--fail-on-gate", action="store_true", help="Exit 1 when hard gates fail.")
60
+ parser.add_argument(
61
+ "--conformance",
62
+ metavar="REVSPEC",
63
+ help="Report how a diff relates to the work order, e.g. `main...HEAD`. "
64
+ "The bounded work order says 'fix exactly these and refactor nothing "
65
+ "else'; without this the bound is an instruction nothing checks. "
66
+ "Reports only unless --fail-on-out-of-scope is passed.",
67
+ )
68
+ parser.add_argument(
69
+ "--fail-on-out-of-scope", action="store_true",
70
+ help="With --conformance, exit 1 when the diff touched files the work "
71
+ "order did not name and that do not pair to one as its test, or "
72
+ "when it added a suppression to a file the work order flagged.",
73
+ )
74
+
75
+
47
76
  def add_arguments(parser: argparse.ArgumentParser) -> None:
48
77
  parser.add_argument("--version", action="version", version=f"maintainability-agent {VERSION}")
49
78
  parser.add_argument("--root", default=".", help="Repository root to scan.")
@@ -110,7 +139,7 @@ def add_arguments(parser: argparse.ArgumentParser) -> None:
110
139
  "The shape of a series is what a trend reads, and a thousand "
111
140
  "commits is hours of work nobody asked for.",
112
141
  )
113
- parser.add_argument("--fail-on-gate", action="store_true", help="Exit 1 when hard gates fail.")
142
+ _add_gate_arguments(parser)
114
143
  _add_setup_actions(parser)
115
144
 
116
145
 
@@ -133,10 +162,19 @@ def _add_setup_actions(parser: argparse.ArgumentParser) -> None:
133
162
  "and remove files the package no longer ships. Without it, a "
134
163
  "differing copy is refused with the list of differences.",
135
164
  )
165
+ parser.add_argument(
166
+ "--target-path",
167
+ action="append",
168
+ metavar="TARGET=PATH",
169
+ help="Where a target's standing instructions go, e.g. "
170
+ "`--target-path bob=.bob/instructions.md`. Required for any agent "
171
+ "this tool has no built-in convention for; overrides the built-in "
172
+ "path for one it does. The target list will always trail the "
173
+ "market, and guessing a path writes a file the agent never opens.",
174
+ )
136
175
  parser.add_argument(
137
176
  "--target",
138
177
  action="append",
139
- choices=["generic", "claude-code", "codex", "cursor", "copilot", "windsurf"],
140
178
  help="Instruction target. Repeatable. Used with --init-agent-standards.",
141
179
  )
142
180
  parser.add_argument("--instructions-output-dir", default=".", help="Directory for generated instruction files.")
@@ -188,6 +226,25 @@ def write_outputs(args: argparse.Namespace, report: dict, rendered: str) -> None
188
226
  json.dumps(report_to_sarif(report), indent=2) + "\n", json_artifact=True)
189
227
 
190
228
 
229
+ def attach_conformance(args: argparse.Namespace, report: dict) -> None:
230
+ """Record how the diff relates to the work order, when asked.
231
+
232
+ Assembly, not scoring: the record is attached to the report and reaches
233
+ no dimension, because whether a diff was obedient is a fact about an
234
+ agent's behaviour and not evidence about the code's condition.
235
+ """
236
+ if not args.conformance:
237
+ return
238
+ from ._conformance import scope_conformance
239
+
240
+ root = Path(report["root"])
241
+ changed = changed_paths(root, args.conformance)
242
+ added = added_lines(root, args.conformance)
243
+ report["scope_conformance"] = scope_conformance(
244
+ report, changed, args.conformance, added
245
+ )
246
+
247
+
191
248
  def audit_exit_code(args: argparse.Namespace, report: dict) -> int:
192
249
  if args.fail_on_new:
193
250
  # Structured matching, never a label-set difference: a label
@@ -198,6 +255,16 @@ def audit_exit_code(args: argparse.Namespace, report: dict) -> int:
198
255
  return 1
199
256
  if args.fail_on_gate and report["hard_gate_failures"]:
200
257
  return 1
258
+ if args.fail_on_out_of_scope:
259
+ record = report.get("scope_conformance")
260
+ if record is None:
261
+ print("--fail-on-out-of-scope needs --conformance REVSPEC", file=sys.stderr)
262
+ return 2
263
+ # `clean`, not `conformant`: staying in scope while silencing the
264
+ # finding is the evasion the pairing exists to catch, and a gate
265
+ # that accepted it would be satisfied by the thing it guards against.
266
+ if not record["clean"]:
267
+ return 1
201
268
  return 0
202
269
 
203
270
 
@@ -258,6 +325,35 @@ def _install_skill_action(args: argparse.Namespace) -> int:
258
325
  return 0
259
326
 
260
327
 
328
+
329
+ def _target_paths(pairs: list[str] | None) -> dict[str, str]:
330
+ """`TARGET=PATH` arguments, as a mapping.
331
+
332
+ An absolute path is refused: the instruction pack is written relative
333
+ to `--instructions-output-dir`, so an absolute one would silently
334
+ ignore the directory the caller named.
335
+ """
336
+ overrides: dict[str, str] = {}
337
+ for pair in pairs or []:
338
+ target, separator, path = pair.partition("=")
339
+ if not separator or not target.strip() or not path.strip():
340
+ raise ValueError(f"--target-path expects TARGET=PATH, got {pair!r}")
341
+ if Path(path).is_absolute():
342
+ raise ValueError(
343
+ f"--target-path {pair!r} is absolute; paths are relative to "
344
+ "--instructions-output-dir"
345
+ )
346
+ overrides[target.strip()] = path.strip()
347
+ return overrides
348
+
349
+
350
+ def _parse(argv: list[str]) -> tuple[argparse.ArgumentParser, argparse.Namespace]:
351
+ """The parser comes back too: `main` still needs it for `parser.error`."""
352
+ parser = argparse.ArgumentParser()
353
+ add_arguments(parser)
354
+ return parser, parser.parse_args(argv)
355
+
356
+
261
357
  def main(argv: list[str] | None = None) -> int:
262
358
  if argv is None:
263
359
  argv = sys.argv[1:]
@@ -266,18 +362,22 @@ def main(argv: list[str] | None = None) -> int:
266
362
 
267
363
  return mcp_server.main(argv[1:])
268
364
 
269
- parser = argparse.ArgumentParser()
270
- add_arguments(parser)
271
- args = parser.parse_args(argv)
272
-
365
+ parser, args = _parse(argv)
273
366
  if args.install_skill:
274
367
  return _install_skill_action(args)
275
368
 
276
369
  root = Path(args.root).resolve()
277
370
  config = _interactive_config(root, args.config)
278
371
  if args.init_agent_standards:
279
- targets = args.target or ["generic", "claude-code", "codex", "cursor", "copilot", "windsurf"]
280
- write_instruction_pack(targets, Path(args.instructions_output_dir).resolve(), config)
372
+ targets = args.target or list(INSTRUCTION_TARGETS)
373
+ try:
374
+ overrides = _target_paths(args.target_path)
375
+ write_instruction_pack(
376
+ targets, Path(args.instructions_output_dir).resolve(), config, overrides
377
+ )
378
+ except (UnknownTarget, ValueError) as failure:
379
+ print(str(failure), file=sys.stderr)
380
+ return 2
281
381
  return 0
282
382
 
283
383
  if args.backfill:
@@ -327,6 +427,9 @@ def main(argv: list[str] | None = None) -> int:
327
427
  # escalated, and only delivered advice is recorded as targeted.
328
428
  record_scan_and_attach(report, config, history_path, Path(report["root"]),
329
429
  record=bool(record), want_targets=bool(args.prompt_output))
430
+ # Before rendering, so the record is in the report every presentation
431
+ # and every consumer reads, rather than only in the exit code.
432
+ attach_conformance(args, report)
330
433
  rendered = _render_presentation(args, report, history_path)
331
434
  write_outputs(args, report, rendered)
332
435
  mark_repo_seen(root) # completed audit: not a first run now, whatever the gate says (D13)
@@ -11,7 +11,7 @@ from ._config_defaults import DEFAULT_CONFIG, DEFAULT_IDIOM_GROUPS
11
11
  # still the door every caller comes through.
12
12
  __all__ = ["DEFAULT_CONFIG", "DEFAULT_IDIOM_GROUPS", "PROJECT_URL", "VERSION"]
13
13
 
14
- VERSION = "2.0.0"
14
+ VERSION = "2.1.0"
15
15
 
16
16
  PROJECT_URL = "https://github.com/marshallguillory86/maintainability-agent"
17
17
 
@@ -229,6 +229,44 @@ def changed_paths(root: Path, revspec: str) -> set[str]:
229
229
  return {line.strip().replace(os.sep, "/") for line in output.splitlines() if line.strip()}
230
230
 
231
231
 
232
+ def added_lines(root: Path, revspec: str) -> dict[str, list[tuple[int, str]]]:
233
+ """Lines a revspec *adds*, per path, as (line number, text).
234
+
235
+ Added lines only. A suppression that was already in the tree is not
236
+ evidence about this change, and reading whole files instead of the diff
237
+ would report a decade of accumulated `# noqa` as though this agent had
238
+ just written them.
239
+
240
+ `--unified=0` keeps context lines out, so every `+` is genuinely new.
241
+ The revspec is validated and `--` terminates the revision list, as in
242
+ `changed_paths`: nothing after it can be read as a path or an option.
243
+ """
244
+ # `--no-ext-diff` is load-bearing, not defensive. The read-only config
245
+ # this module runs under neutralises `diff.external`, which costs
246
+ # nothing for `--name-only` because no diff is produced — and kills a
247
+ # content diff outright, because git then tries to execute the empty
248
+ # string: "cannot run : No such file or directory".
249
+ output = run_git(
250
+ ["diff", "--no-ext-diff", "--unified=0", validate_revspec(revspec), "--"], root
251
+ )
252
+ added: dict[str, list[tuple[int, str]]] = {}
253
+ path = ""
254
+ line_number = 0
255
+ for line in output.splitlines():
256
+ if line.startswith("+++ b/"):
257
+ path = line[6:].strip().replace(os.sep, "/")
258
+ elif line.startswith("@@"):
259
+ # @@ -old,count +new,count @@
260
+ marker = line.split("+", 1)[1].split("@@", 1)[0].strip()
261
+ start = marker.split(",", 1)[0]
262
+ line_number = int(start) if start.lstrip("-").isdigit() else 0
263
+ elif line.startswith("+") and not line.startswith("+++"):
264
+ if path:
265
+ added.setdefault(path, []).append((line_number, line[1:]))
266
+ line_number += 1
267
+ return added
268
+
269
+
232
270
  def worktree_status(root: Path) -> str | None:
233
271
  """Porcelain status, or `None` when git cannot answer.
234
272
 
@@ -81,19 +81,64 @@ def instruction_body(target: str, config: dict[str, Any]) -> str:
81
81
  return "\n".join(lines)
82
82
 
83
83
 
84
- def instruction_path_for_target(target: str, output_dir: Path) -> Path:
85
- mapping = {
86
- "generic": "AI-MAINTAINABILITY.md",
87
- "claude-code": "CLAUDE.md",
88
- "codex": "AGENTS.md",
89
- "cursor": ".cursor/rules/maintainability.mdc",
90
- "copilot": ".github/copilot-instructions.md",
91
- "windsurf": ".windsurf/rules/maintainability.md",
92
- }
93
- return output_dir / mapping.get(target, f"{target}-maintainability.md")
84
+ #: Each agent and the file it actually reads for repo standing
85
+ #: instructions. One declaration, because this list was written out three
86
+ #: times — here, and twice in `cli.py` as `choices` and as the default —
87
+ #: so adding a target meant remembering all three or shipping one the CLI
88
+ #: refused to accept.
89
+ #:
90
+ #: A target belongs here only when the path it writes is the path that
91
+ #: agent genuinely reads. Emitting a plausible-looking file an agent never
92
+ #: opens is worse than having no target: it looks configured and does
93
+ #: nothing.
94
+ INSTRUCTION_TARGETS: dict[str, str] = {
95
+ "generic": "AI-MAINTAINABILITY.md",
96
+ "claude-code": "CLAUDE.md",
97
+ "codex": "AGENTS.md",
98
+ "cursor": ".cursor/rules/maintainability.mdc",
99
+ "copilot": ".github/copilot-instructions.md",
100
+ "windsurf": ".windsurf/rules/maintainability.md",
101
+ }
94
102
 
95
103
 
96
- def write_instruction_pack(targets: list[str], output_dir: Path, config: dict[str, Any]) -> list[str]:
104
+ class UnknownTarget(ValueError):
105
+ """A target with no built-in convention and no path supplied."""
106
+
107
+
108
+ def instruction_path_for_target(
109
+ target: str, output_dir: Path, overrides: dict[str, str] | None = None
110
+ ) -> Path:
111
+ """Where this agent's standing instructions go.
112
+
113
+ An override wins over the built-in table, because a convention can
114
+ change or be site-specific, and the person running the command knows
115
+ their agent better than this table does.
116
+
117
+ A target nobody has a convention for is **refused** rather than given a
118
+ generated filename. The fallback that used to sit here —
119
+ ``{target}-maintainability.md`` — was unreachable anyway, because the
120
+ CLI's `choices` rejected unknown targets first; and had it been
121
+ reachable it would have written a plausible-looking file the agent
122
+ never opens, which is worse than no target at all. It looks configured
123
+ and does nothing.
124
+ """
125
+ if overrides and target in overrides:
126
+ return output_dir / overrides[target]
127
+ if target in INSTRUCTION_TARGETS:
128
+ return output_dir / INSTRUCTION_TARGETS[target]
129
+ raise UnknownTarget(
130
+ f"no known instructions path for {target!r}. Pass "
131
+ f"--target-path {target}=PATH to say where that agent reads its "
132
+ f"standing instructions. Known targets: {', '.join(INSTRUCTION_TARGETS)}"
133
+ )
134
+
135
+
136
+ def write_instruction_pack(
137
+ targets: list[str],
138
+ output_dir: Path,
139
+ config: dict[str, Any],
140
+ overrides: dict[str, str] | None = None,
141
+ ) -> list[str]:
97
142
  # Each instruction file is a product-artifact write: `output_dir`
98
143
  # comes from a person (`--instructions-output-dir`), so the write is
99
144
  # bound to that directory and refuses a symlinked route the audited
@@ -102,7 +147,7 @@ def write_instruction_pack(targets: list[str], output_dir: Path, config: dict[st
102
147
 
103
148
  written: list[str] = []
104
149
  for target in targets:
105
- path = instruction_path_for_target(target, output_dir)
150
+ path = instruction_path_for_target(target, output_dir, overrides)
106
151
  write_artifact(output_dir, path, instruction_body(target, config) + "\n")
107
152
  written.append(str(path))
108
153
  return written