maintainability-agent 3.2.0__tar.gz → 3.3.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 (375) hide show
  1. {maintainability_agent-3.2.0/src/maintainability_agent.egg-info → maintainability_agent-3.3.0}/PKG-INFO +2 -2
  2. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/README.md +1 -1
  3. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/pyproject.toml +1 -1
  4. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0/src/maintainability_agent.egg-info}/PKG-INFO +2 -2
  5. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_agent.egg-info/SOURCES.txt +2 -0
  6. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/__init__.py +1 -1
  7. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_arguments.py +9 -0
  8. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_coverage_notes.py +36 -0
  9. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_criteria_scope.py +37 -31
  10. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_delegated_pillar.py +24 -1
  11. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_html_report_sections.py +23 -0
  12. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_mcp_audit.py +36 -4
  13. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_pillars.py +7 -0
  14. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_scan_history.py +94 -5
  15. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_test_execution.py +81 -15
  16. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/cli.py +2 -1
  17. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/config.py +1 -1
  18. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/duplication.py +21 -10
  19. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/history.py +58 -28
  20. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/report.py +8 -5
  21. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_bands.py +80 -0
  22. maintainability_agent-3.3.0/tests/test_child_sandbox.py +203 -0
  23. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_delegated_security_pillar.py +72 -2
  24. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_docs_links.py +30 -0
  25. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_html_section_parity_class.py +21 -1
  26. maintainability_agent-3.3.0/tests/test_mcp_format_contract.py +22 -0
  27. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_mcp_server.py +33 -0
  28. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_operator_named_reads.py +5 -1
  29. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_test_execution_class.py +28 -8
  30. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_tree_cannot_opt_the_host_in.py +61 -3
  31. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/LICENSE +0 -0
  32. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/setup.cfg +0 -0
  33. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_agent.egg-info/dependency_links.txt +0 -0
  34. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_agent.egg-info/entry_points.txt +0 -0
  35. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_agent.egg-info/requires.txt +0 -0
  36. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_agent.egg-info/top_level.txt +0 -0
  37. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/__main__.py +0 -0
  38. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_adapters.py +0 -0
  39. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_analysis.py +0 -0
  40. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_analyzer_sections.py +0 -0
  41. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_anchor.py +0 -0
  42. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_aspects.py +0 -0
  43. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_assets/analyzer-catalog.json +0 -0
  44. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_assets/standard.md +0 -0
  45. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_attestation.py +0 -0
  46. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_backfill.py +0 -0
  47. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_bands.py +0 -0
  48. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_banner.py +0 -0
  49. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_built_ins.py +0 -0
  50. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_calibration.py +0 -0
  51. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_catalog.py +0 -0
  52. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_charts.py +0 -0
  53. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_cognitive.py +0 -0
  54. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_config_defaults.py +0 -0
  55. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_conformance.py +0 -0
  56. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_corroborate.py +0 -0
  57. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_derive.py +0 -0
  58. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_discovery.py +0 -0
  59. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_documents.py +0 -0
  60. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_economics.py +0 -0
  61. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_economics_view.py +0 -0
  62. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_environment.py +0 -0
  63. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_evidence_reader.py +0 -0
  64. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_evidence_view.py +0 -0
  65. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_finding_match.py +0 -0
  66. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_first_run.py +0 -0
  67. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_formula.py +0 -0
  68. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_gates.py +0 -0
  69. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_generic.py +0 -0
  70. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_grammar.py +0 -0
  71. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_grant_ledger.py +0 -0
  72. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_history_view.py +0 -0
  73. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_hostile_prompt.py +0 -0
  74. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_hotspots.py +0 -0
  75. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_html_view.py +0 -0
  76. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_identity.py +0 -0
  77. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_in_loop.py +0 -0
  78. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_in_loop_view.py +0 -0
  79. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_jvm_adapters.py +0 -0
  80. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_markdown_sections.py +0 -0
  81. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_masking.py +0 -0
  82. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_mcp_gate.py +0 -0
  83. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_mcp_grants.py +0 -0
  84. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_mcp_refusals.py +0 -0
  85. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_mcp_resources.py +0 -0
  86. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_mcp_setup.py +0 -0
  87. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_metric_adapters.py +0 -0
  88. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_metrics_types.py +0 -0
  89. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_operator_reads.py +0 -0
  90. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_practice.py +0 -0
  91. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_precommit.py +0 -0
  92. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_precommit_install.py +0 -0
  93. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_precommit_view.py +0 -0
  94. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_pressures.py +0 -0
  95. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_prompt_sections.py +0 -0
  96. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_c.py +0 -0
  97. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_cobol.py +0 -0
  98. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_core.py +0 -0
  99. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_cpp.py +0 -0
  100. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_csharp.py +0 -0
  101. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_fortran.py +0 -0
  102. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_go.py +0 -0
  103. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_java.py +0 -0
  104. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_js.py +0 -0
  105. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_php.py +0 -0
  106. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_ruby.py +0 -0
  107. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_rust.py +0 -0
  108. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ranges_swift.py +0 -0
  109. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ratchet.py +0 -0
  110. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_ratio_adapters.py +0 -0
  111. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_recurrence.py +0 -0
  112. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_run_comparison.py +0 -0
  113. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_runner.py +0 -0
  114. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_safe_write.py +0 -0
  115. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_scan_view.py +0 -0
  116. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_second_source.py +0 -0
  117. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_selection.py +0 -0
  118. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_semantic.py +0 -0
  119. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_semantic_policy.py +0 -0
  120. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_semantic_ts.py +0 -0
  121. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_semantic_view.py +0 -0
  122. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_setup_errors.py +0 -0
  123. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_setup_persist.py +0 -0
  124. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_skill_data/SKILL.md +0 -0
  125. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_skill_data/agents/anthropic.yaml +0 -0
  126. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_skill_data/agents/copilot.yaml +0 -0
  127. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_skill_data/agents/openai.yaml +0 -0
  128. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_skill_data/copilot/maintainability-agent.prompt.md +0 -0
  129. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_skill_data/references/finding-taxonomy.md +0 -0
  130. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_skill_install.py +0 -0
  131. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_stored_grants.py +0 -0
  132. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_tdd_view.py +0 -0
  133. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_test_commands.py +0 -0
  134. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_test_pairing.py +0 -0
  135. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_tokens.py +0 -0
  136. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_tool_adapters.py +0 -0
  137. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_trends.py +0 -0
  138. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_user_config.py +0 -0
  139. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_verdict_adapters.py +0 -0
  140. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_verification.py +0 -0
  141. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_work_order.py +0 -0
  142. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_work_order_view.py +0 -0
  143. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_work_order_weights.py +0 -0
  144. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/_xml.py +0 -0
  145. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/baseline.py +0 -0
  146. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/deadcode.py +0 -0
  147. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/declarations.py +0 -0
  148. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/evidence.py +0 -0
  149. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/git_tools.py +0 -0
  150. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/idioms.py +0 -0
  151. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/instructions.py +0 -0
  152. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/mcp_server.py +0 -0
  153. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/metrics.py +0 -0
  154. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/prompts.py +0 -0
  155. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/renderers.py +0 -0
  156. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/sarif.py +0 -0
  157. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/scoring.py +0 -0
  158. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/similarity.py +0 -0
  159. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/src/maintainability_audit/source.py +0 -0
  160. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_acquisition_trust.py +0 -0
  161. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_adapter_docs.py +0 -0
  162. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_adapter_recordings.py +0 -0
  163. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_adapters.py +0 -0
  164. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_added_lines.py +0 -0
  165. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analysis_coverage.py +0 -0
  166. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analysis_execution.py +0 -0
  167. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_bridge.py +0 -0
  168. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_config_isolation.py +0 -0
  169. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_estimate_claims.py +0 -0
  170. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_pinning.py +0 -0
  171. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_primary_estimate.py +0 -0
  172. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_provenance.py +0 -0
  173. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_provenance_exclusions.py +0 -0
  174. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_scoring_path.py +0 -0
  175. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_spread_range.py +0 -0
  176. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_analyzer_xml_bounds.py +0 -0
  177. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_anchor_and_mode.py +0 -0
  178. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_anchor_disclosure.py +0 -0
  179. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_annotations_match_behaviour.py +0 -0
  180. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_anticipated_refusals.py +0 -0
  181. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_architecture.py +0 -0
  182. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_artifact_write_route_class.py +0 -0
  183. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_aspect_coverage.py +0 -0
  184. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_asset_snapshot_class.py +0 -0
  185. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_ast_reading_resolves_spellings.py +0 -0
  186. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_attestation.py +0 -0
  187. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_audit_components.py +0 -0
  188. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_authorization_freshness.py +0 -0
  189. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_authorship_gates.py +0 -0
  190. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_backfill.py +0 -0
  191. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_band_concealment_class.py +0 -0
  192. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_band_concealment_p3.py +0 -0
  193. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_band_pressures.py +0 -0
  194. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_body_or_signature_class.py +0 -0
  195. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_c_declarations.py +0 -0
  196. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_calibration_corpus.py +0 -0
  197. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_calibration_drift.py +0 -0
  198. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_calibration_tooling.py +0 -0
  199. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_catalog_selection.py +0 -0
  200. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_catalog_tier_promise.py +0 -0
  201. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_chat_primary_docs.py +0 -0
  202. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_check_refuses_plus_only_fragments.py +0 -0
  203. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_checkstyle_adapter.py +0 -0
  204. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_checkstyle_catalog.py +0 -0
  205. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_ci_history_recipe.py +0 -0
  206. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_ci_installs_the_analyzer_pool.py +0 -0
  207. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_claimed_languages.py +0 -0
  208. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_cli.py +0 -0
  209. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_clone_group_class.py +0 -0
  210. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_cobol_declarations.py +0 -0
  211. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_cognitive_complexity.py +0 -0
  212. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_cohort_statistics.py +0 -0
  213. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_concealment_properties.py +0 -0
  214. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_config_shape.py +0 -0
  215. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_consent_and_grant.py +0 -0
  216. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_consumer_migration.py +0 -0
  217. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_contract_claims.py +0 -0
  218. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_corroborate.py +0 -0
  219. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_coverage_by_language.py +0 -0
  220. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_cpp_declarations.py +0 -0
  221. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_criteria_completion.py +0 -0
  222. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_csharp_declarations.py +0 -0
  223. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_d15_composition.py +0 -0
  224. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_d15_goal_directed.py +0 -0
  225. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_dead_code.py +0 -0
  226. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_declaration_criteria_scope.py +0 -0
  227. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_declaration_edge_cases.py +0 -0
  228. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_declaration_grading.py +0 -0
  229. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_declaration_ranges.py +0 -0
  230. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_declaration_source.py +0 -0
  231. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_declarative_parsers.py +0 -0
  232. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_declared_authorship.py +0 -0
  233. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_declared_imports.py +0 -0
  234. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_derivation_mix.py +0 -0
  235. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_determinism.py +0 -0
  236. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_dimension_ratchet.py +0 -0
  237. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_direct_runner_availability_class.py +0 -0
  238. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_discovery.py +0 -0
  239. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_doc_claims.py +0 -0
  240. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_economic_context.py +0 -0
  241. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_empty_output_not_clean.py +0 -0
  242. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_empty_output_not_clean_adapters.py +0 -0
  243. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_environment_work_order.py +0 -0
  244. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_evidence_invariants.py +0 -0
  245. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_evidence_normalization.py +0 -0
  246. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_evidence_properties.py +0 -0
  247. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_exclusion_dialects.py +0 -0
  248. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_falsifier_standard.py +0 -0
  249. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_file_list_cap_class.py +0 -0
  250. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_finding_identity.py +0 -0
  251. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_first_run_elicitation.py +0 -0
  252. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_first_run_prompt.py +0 -0
  253. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_first_run_reelicitation.py +0 -0
  254. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_fix_breadth_window.py +0 -0
  255. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_fixed_form_fortran.py +0 -0
  256. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_format_ask.py +0 -0
  257. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_fortran_declarations.py +0 -0
  258. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_fortran_metrics.py +0 -0
  259. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_fortran_practice.py +0 -0
  260. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_git_argv.py +0 -0
  261. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_git_read_only.py +0 -0
  262. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_git_readonly_class.py +0 -0
  263. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_go_declarations.py +0 -0
  264. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_grammar_constructs.py +0 -0
  265. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_grammar_token_coverage.py +0 -0
  266. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_grant_only_user_tier.py +0 -0
  267. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_history.py +0 -0
  268. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_history_boundary.py +0 -0
  269. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_history_manifest.py +0 -0
  270. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_history_schema2.py +0 -0
  271. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_history_window.py +0 -0
  272. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_hostile_prompt.py +0 -0
  273. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_html_executive.py +0 -0
  274. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_identity_docs.py +0 -0
  275. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_identity_resolution.py +0 -0
  276. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_idiom_divergence.py +0 -0
  277. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_in_loop_budgets.py +0 -0
  278. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_in_loop_check.py +0 -0
  279. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_installed_wheel_runtime.py +0 -0
  280. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_instruction_targets.py +0 -0
  281. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_java_range_guard.py +0 -0
  282. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_java_ranges.py +0 -0
  283. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_java_scored.py +0 -0
  284. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_java_wiring.py +0 -0
  285. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_js_complexity_operators.py +0 -0
  286. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_js_declarations.py +0 -0
  287. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_language_coverage.py +0 -0
  288. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_language_support_honesty.py +0 -0
  289. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_mcp_baseline_payload.py +0 -0
  290. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_mcp_history.py +0 -0
  291. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_migration_1_0.py +0 -0
  292. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_near_duplicates.py +0 -0
  293. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_network_disclosure.py +0 -0
  294. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_one_setup_across_surfaces_class.py +0 -0
  295. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_operator_named_paths.py +0 -0
  296. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_pairing_reads_imports.py +0 -0
  297. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_pattern_budget.py +0 -0
  298. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_phase6_claims.py +0 -0
  299. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_php_declarations.py +0 -0
  300. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_pillars_and_practice.py +0 -0
  301. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_platform_claim.py +0 -0
  302. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_pmd_adapter.py +0 -0
  303. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_pmd_audit_closures.py +0 -0
  304. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_policy_claims.py +0 -0
  305. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_pool_runs_by_config.py +0 -0
  306. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_population_floors.py +0 -0
  307. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_practice_nested_gates_class.py +0 -0
  308. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_precommit_staged.py +0 -0
  309. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_presentation_refusals.py +0 -0
  310. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_promises.py +0 -0
  311. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_prompt_escalation_once.py +0 -0
  312. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_prompt_honesty.py +0 -0
  313. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_prompt_location_escalation_class.py +0 -0
  314. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_prove_falsifiers.py +0 -0
  315. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_python_complexity.py +0 -0
  316. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_python_declarations.py +0 -0
  317. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_read_boundary.py +0 -0
  318. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_readme_claims.py +0 -0
  319. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_recurrence.py +0 -0
  320. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_release_needs_an_empty_ledger.py +0 -0
  321. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_release_plan.py +0 -0
  322. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_release_plan_status.py +0 -0
  323. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_report_remedy_copy.py +0 -0
  324. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_roles_recorded.py +0 -0
  325. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_root_grants.py +0 -0
  326. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_rubric_ignores_project_lint_config.py +0 -0
  327. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_ruby_declarations.py +0 -0
  328. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_run_comparison.py +0 -0
  329. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_runner.py +0 -0
  330. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_rust_declarations.py +0 -0
  331. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_sarif.py +0 -0
  332. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_scan_history.py +0 -0
  333. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_scan_scope.py +0 -0
  334. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_scanner_unsafe_direction.py +0 -0
  335. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_scanning.py +0 -0
  336. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_schema_fidelity.py +0 -0
  337. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_scope_conformance.py +0 -0
  338. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_scoring_calibration.py +0 -0
  339. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_semantic_policy.py +0 -0
  340. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_setup_gate_completeness.py +0 -0
  341. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_setup_gate_independent_contract.py +0 -0
  342. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_setup_precondition.py +0 -0
  343. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_skill_install.py +0 -0
  344. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_sonar_resolve.py +0 -0
  345. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_source_index.py +0 -0
  346. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_spotbugs_adapter.py +0 -0
  347. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_spotbugs_catalog.py +0 -0
  348. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_stage8_contract.py +0 -0
  349. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_suite_isolation.py +0 -0
  350. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_swift_declarations.py +0 -0
  351. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_tdd_structure.py +0 -0
  352. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_terminal_asks_the_same_questions.py +0 -0
  353. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_test_commands.py +0 -0
  354. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_three_presentations.py +0 -0
  355. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_tool_resolution.py +0 -0
  356. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_tree_chosen_spawn.py +0 -0
  357. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_tree_reads_use_the_primitive.py +0 -0
  358. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_trends.py +0 -0
  359. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_type_fields_not_functions_class.py +0 -0
  360. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_uat_falsifiers.py +0 -0
  361. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_unanchored_set_matches_corpus.py +0 -0
  362. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_unread_code.py +0 -0
  363. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_user_config_tier.py +0 -0
  364. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_verification_audit_closures.py +0 -0
  365. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_verified_grade.py +0 -0
  366. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_version_claim.py +0 -0
  367. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_wheel_contents.py +0 -0
  368. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_work_order.py +0 -0
  369. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_work_order_binds_to_this_interpreter.py +0 -0
  370. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_work_order_items.py +0 -0
  371. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_work_order_selection.py +0 -0
  372. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_workflow_supply_chain.py +0 -0
  373. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_write_boundary.py +0 -0
  374. {maintainability_agent-3.2.0 → maintainability_agent-3.3.0}/tests/test_write_route_class.py +0 -0
  375. {maintainability_agent-3.2.0 → maintainability_agent-3.3.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: 3.2.0
3
+ Version: 3.3.0
4
4
  Summary: Maintainability gate and AI remediation prompt generator for CI.
5
5
  Author: Marshall Guillory
6
6
  License: MIT
@@ -45,7 +45,7 @@ Dynamic: license-file
45
45
  **A deterministic, offline maintainability audit whose output is a _bounded
46
46
  work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
47
47
  says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
48
- Version **3.2.0**.
48
+ Version **3.3.0**.
49
49
 
50
50
  **Planned, not shipped:** the audit tool will become `maintainability-audit`;
51
51
  a new `maintainability-agent` companion will run it and automate human-selected
@@ -5,7 +5,7 @@
5
5
  **A deterministic, offline maintainability audit whose output is a _bounded
6
6
  work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
7
7
  says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
8
- Version **3.2.0**.
8
+ Version **3.3.0**.
9
9
 
10
10
  **Planned, not shipped:** the audit tool will become `maintainability-audit`;
11
11
  a new `maintainability-agent` companion will run it and automate human-selected
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "maintainability-agent"
7
- version = "3.2.0"
7
+ version = "3.3.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: 3.2.0
3
+ Version: 3.3.0
4
4
  Summary: Maintainability gate and AI remediation prompt generator for CI.
5
5
  Author: Marshall Guillory
6
6
  License: MIT
@@ -45,7 +45,7 @@ Dynamic: license-file
45
45
  **A deterministic, offline maintainability audit whose output is a _bounded
46
46
  work order_ for an AI coding agent** — a copy-paste prompt, per finding, that
47
47
  says *fix exactly these and refactor nothing else*. Chat-primary; CLI for CI.
48
- Version **3.2.0**.
48
+ Version **3.3.0**.
49
49
 
50
50
  **Planned, not shipped:** the audit tool will become `maintainability-audit`;
51
51
  a new `maintainability-agent` companion will run it and automate human-selected
@@ -191,6 +191,7 @@ tests/test_chat_primary_docs.py
191
191
  tests/test_check_refuses_plus_only_fragments.py
192
192
  tests/test_checkstyle_adapter.py
193
193
  tests/test_checkstyle_catalog.py
194
+ tests/test_child_sandbox.py
194
195
  tests/test_ci_history_recipe.py
195
196
  tests/test_ci_installs_the_analyzer_pool.py
196
197
  tests/test_claimed_languages.py
@@ -279,6 +280,7 @@ tests/test_js_declarations.py
279
280
  tests/test_language_coverage.py
280
281
  tests/test_language_support_honesty.py
281
282
  tests/test_mcp_baseline_payload.py
283
+ tests/test_mcp_format_contract.py
282
284
  tests/test_mcp_history.py
283
285
  tests/test_mcp_server.py
284
286
  tests/test_migration_1_0.py
@@ -1,3 +1,3 @@
1
1
  """Maintainability Audit CI."""
2
2
 
3
- __version__ = "3.2.0"
3
+ __version__ = "3.3.0"
@@ -120,6 +120,15 @@ def add_arguments(parser: argparse.ArgumentParser) -> None:
120
120
  parser.add_argument("--output", help="Output file. Defaults to stdout.")
121
121
  _add_artifact_arguments(parser)
122
122
  parser.add_argument("--sarif-output", help="Optional SARIF output path for GitHub code scanning.")
123
+ parser.add_argument(
124
+ "--security-pillar",
125
+ help=(
126
+ "Path to the security-pillar.json secure-code-agent writes with its "
127
+ "own --security-pillar. ADR 007 delegates the security pillar, and "
128
+ "this is the document it is read from; defaults to "
129
+ ".maintainability/security-pillar.json."
130
+ ),
131
+ )
123
132
  parser.add_argument("--sarif-input", action="append", help="Optional external SARIF file to summarize in reports. Repeatable.")
124
133
  parser.add_argument("--changed-only", help="Audit only files changed in a git revspec, for example main...HEAD.")
125
134
  parser.add_argument("--baseline", help="Existing baseline JSON. With --fail-on-new, only new findings fail.")
@@ -59,4 +59,40 @@ def coverage_notes(coverage: dict[str, Any]) -> list[str]:
59
59
  "or widen `analyzers.depth`, to have them reported.",
60
60
  "",
61
61
  ])
62
+
63
+ lines.extend(_child_boundary_note(coverage))
62
64
  return lines
65
+
66
+
67
+ def _child_boundary_note(coverage: dict[str, Any]) -> list[str]:
68
+ """What this run does not control about the tools it spawned.
69
+
70
+ `docs/product-intent.md` has said since P1 was amended that this
71
+ package "does not sandbox children", and lists *"that third-party
72
+ tools cannot use the network"* among the things it does not promise.
73
+ The **report** did not say it. A reader holding a page headed "the
74
+ analysis itself performs no network access" and a table naming the
75
+ external tools that ran had no way to learn the second sentence does
76
+ not cover the first.
77
+
78
+ That makes it P8's gap rather than P1's: the promise was correctly
79
+ scoped all along, and the run relying on that scoping never disclosed
80
+ it to the person reading the output.
81
+
82
+ Only when an external tool actually ran. With no children spawned
83
+ there is nothing to disclose, and saying it anyway would be the noise
84
+ this section is careful to avoid.
85
+ """
86
+ spawned = coverage.get("tools_contributed") or 0
87
+ if not spawned:
88
+ return []
89
+ return [
90
+ "**Analyzer children are not network-isolated.** "
91
+ f"{spawned} external {'tool' if spawned == 1 else 'tools'} ran as local "
92
+ "child processes. This agent does not transmit the audited source and "
93
+ "opens no socket of its own, but it does not sandbox what it spawns: a "
94
+ "third-party analyzer that reaches the network is outside what this run "
95
+ "controls or observes. Determinism and no upload are the promise; a "
96
+ "kernel air-gap is not.",
97
+ "",
98
+ ]
@@ -154,40 +154,46 @@ def complete_from_built_ins(
154
154
  """
155
155
  completed: dict[str, dict[str, float]] = {}
156
156
  for unit, values in per_unit.items():
157
- missing = declaration_concepts_missing(set(values))
158
- if not missing:
159
- completed[unit] = values
160
- continue
161
- location = unit_locations.get(unit)
162
- fallback = built_in.get(location) if location else None
163
- filled = dict(values)
164
- for concept in missing:
165
- if fallback and concept in fallback:
166
- filled[concept] = fallback[concept]
167
- if declaration_concepts_missing(set(filled)):
168
- # No counterpart in the scanner's reading, so this
169
- # declaration is not scoreable on the full criterion set by
170
- # either tier — and it is dropped rather than left to
171
- # disqualify its language.
172
- #
173
- # Measured, because the alternative looked reasonable and was
174
- # not: `lizard` reads files the built-in scanner produces no
175
- # declarations for — Alamofire's `docs/js/jquery.min.js` and
176
- # `jazzy.js`, five generated documentation assets. Keeping
177
- # them gave JavaScript a bucket that could never be completed
178
- # and took the whole repository to the built-in tier, Swift
179
- # and all. The same shape hid Java behind a repository's
180
- # bundled `.js`.
181
- #
182
- # Dropping them also aligns the two populations: a unit the
183
- # scanner never read is not in the built-in rate either, so
184
- # including it in the analyzer rate compares a wider
185
- # population against a narrower one.
186
- continue
187
- completed[unit] = filled
157
+ filled = _completed_unit(values, unit_locations.get(unit), built_in)
158
+ if filled is not None:
159
+ completed[unit] = filled
188
160
  return completed
189
161
 
190
162
 
163
+ def _completed_unit(
164
+ values: dict[str, float],
165
+ location: tuple[str, int] | None,
166
+ built_in: dict[tuple[str, int], dict[str, float]],
167
+ ) -> dict[str, float] | None:
168
+ """One declaration's readings, completed from the built-in tier.
169
+
170
+ `None` means the union of both tiers still does not cover the
171
+ criterion set, so this declaration is **dropped** rather than left to
172
+ disqualify its language.
173
+
174
+ Measured, because the alternative looked reasonable and was not:
175
+ `lizard` reads files the built-in scanner produces no declarations
176
+ for — Alamofire's `docs/js/jquery.min.js` and `jazzy.js`, five
177
+ generated documentation assets. Keeping them gave JavaScript a bucket
178
+ that could never be completed and took the whole repository to the
179
+ built-in tier, Swift and all. The same shape hid Java behind a
180
+ repository's bundled `.js`.
181
+
182
+ Dropping them also aligns the two populations: a unit the scanner
183
+ never read is not in the built-in rate either, so including it in the
184
+ analyzer rate compares a wider population against a narrower one.
185
+ """
186
+ missing = declaration_concepts_missing(set(values))
187
+ if not missing:
188
+ return values
189
+ fallback = (built_in.get(location) if location else None) or {}
190
+ filled = dict(values)
191
+ for concept in missing:
192
+ if concept in fallback:
193
+ filled[concept] = fallback[concept]
194
+ return None if declaration_concepts_missing(set(filled)) else filled
195
+
196
+
191
197
  def built_in_readings(
192
198
  metrics: object, root: str = ""
193
199
  ) -> dict[tuple[str, int], dict[str, float]]:
@@ -58,6 +58,19 @@ DEFAULT_PILLAR_PATH = ".maintainability/security-pillar.json"
58
58
  #: producer's promise about the shape, and guessing past it is how a
59
59
  #: consumer starts reporting fields that mean something different.
60
60
  SCHEMA = "secure-code-agent/security-pillar"
61
+ #: Versions of that contract this tool can read. **Two, deliberately.**
62
+ #: v2 is v1 plus `scoring_model` and nothing else, so a reader that
63
+ #: handles both is not guessing: the producer asserts the key-set
64
+ #: difference is exactly that one field.
65
+ #:
66
+ #: Accepting v2 before the producer emits it is what removes the
67
+ #: deadlock. If MA required v1 while `secure-code-agent` shipped v2,
68
+ #: every document would be refused for the window between the two
69
+ #: releases -- silently, since a refused document reads as no delegated
70
+ #: pillar. Either side can now release first.
71
+ SCHEMA_VERSIONS = frozenset({1, 2})
72
+
73
+ #: Kept for callers that imported the old name. The gate reads the set.
61
74
  SCHEMA_VERSION = 1
62
75
 
63
76
  #: What the pillar block takes from the document. Named rather than
@@ -75,6 +88,16 @@ CARRIED = (
75
88
  "findings_by_severity",
76
89
  "reported_not_scored",
77
90
  "loc_scanned",
91
+ #: v2. The producer's own identifier for its scoring model, bumped
92
+ #: when a repository's condition could differ for reasons that are
93
+ #: not the repository -- normalizer, slope, weights, letter bands,
94
+ #: the scanner floor, or the built-in rule profile. Absent on v1.
95
+ #:
96
+ #: This is what MA keys a delegated trend on, in preference to the
97
+ #: producer's version, because the version moves on docs releases
98
+ #: that change no score and fragmenting a trend for those teaches a
99
+ #: reader to ignore the breaks (D155).
100
+ "scoring_model",
78
101
  )
79
102
 
80
103
 
@@ -104,7 +127,7 @@ def read_delegated(root: Path, relative: str = DEFAULT_PILLAR_PATH) -> dict[str,
104
127
  return None
105
128
  if payload.get("schema") != SCHEMA:
106
129
  return None
107
- if payload.get("schema_version") != SCHEMA_VERSION:
130
+ if payload.get("schema_version") not in SCHEMA_VERSIONS:
108
131
  return None
109
132
  return payload if _is_well_formed(payload) else None
110
133
 
@@ -14,6 +14,7 @@ from __future__ import annotations
14
14
  from html import escape
15
15
  from typing import Any
16
16
 
17
+ from ._coverage_notes import coverage_notes
17
18
  from ._evidence_view import test_suite_lines
18
19
  from ._grammar import counted
19
20
  from ._hotspots import hotspot_cognitive, hotspot_complexity, hotspot_name
@@ -55,9 +56,31 @@ def coverage_section(report: dict[str, Any]) -> list[str]:
55
56
  f"<td>{escape(str(entry.get('measurements', '—')))}</td>"
56
57
  f"<td>{escape(str(entry.get('findings', '—')))}</td></tr>")
57
58
  parts.append("</table>")
59
+ parts.extend(_coverage_notes_html(coverage))
58
60
  return parts
59
61
 
60
62
 
63
+ def _coverage_notes_html(coverage: dict[str, Any]) -> list[str]:
64
+ """The gap prose the Markdown skin prints, in the HTML skin too.
65
+
66
+ The table answers "what ran". These notes answer what a reader asks
67
+ next — one source only, a dimension the analyzer tier declined,
68
+ nothing examined — and the HTML report carried **none** of them,
69
+ including "Nothing examined", which `_coverage_notes` itself calls
70
+ the point of the whole section. ADR 011 says the skins render one
71
+ report dict and never disagree; that claim was resting on the prose
72
+ being built as Markdown strings inside the Markdown renderer.
73
+
74
+ Rendered as the same visible sentences rather than re-marked-up:
75
+ the notes carry `**bold**` and `` `code` `` for Markdown, and a
76
+ reader of the page should see the text, not the syntax.
77
+ """
78
+ return [
79
+ f"<p>{escape(line.replace('**', '').replace('`', ''))}</p>"
80
+ for line in coverage_notes(coverage) if line
81
+ ]
82
+
83
+
61
84
  def _current_series_html(history: list[dict[str, Any]]) -> list[str]:
62
85
  """The series in force, in full — the one a reader acts on today."""
63
86
  segment = history[-1]
@@ -47,6 +47,7 @@ from ._work_order import prompt_targets
47
47
  from .baseline import finding_fingerprints
48
48
  from .config import (
49
49
  VERSION,
50
+ _refuse_symlinked_route,
50
51
  analyzers_run_default,
51
52
  discovered_config,
52
53
  load_config,
@@ -122,6 +123,20 @@ def _check_freshness_when_a_grant_is_the_only_cover(
122
123
 
123
124
 
124
125
  def authorize_config(config_path: str | None, root: Path) -> str | None:
126
+ """The config a caller named, or a refusal — bounded like every other path.
127
+
128
+ Two rules, and this door enforced only one of them. Containment on
129
+ the *resolved* path catches a config outside the repository. It
130
+ cannot catch an **inward** link — `root/link -> root/src` — because
131
+ that resolves to a path inside the repository and passes every
132
+ containment check there is. That escape shows on the lexical route
133
+ only, which is why `repository_path` walks it (D34).
134
+
135
+ So the two doors onto one rule disagreed: `repository_path` refused
136
+ `link/…` and this returned the target. The baseline seam below
137
+ already carries a comment recording this same escape being found and
138
+ closed *there*; this door was never revisited (Grok, 673e667).
139
+ """
125
140
  if config_path is None:
126
141
  return None
127
142
  config = _resolved(config_path, relative_to=root)
@@ -137,6 +152,11 @@ def authorize_config(config_path: str | None, root: Path) -> str | None:
137
152
  "audited. A config file must live inside the repository it "
138
153
  "configures."
139
154
  )
155
+ # After containment, on the route as written rather than as resolved.
156
+ # `repository_path` orders it the same way, and the shared helper is
157
+ # what keeps the two doors from drifting apart again.
158
+ candidate = Path(config_path).expanduser()
159
+ _refuse_symlinked_route(root, candidate if candidate.is_absolute() else root / candidate)
140
160
  return str(config)
141
161
 
142
162
 
@@ -415,10 +435,22 @@ def _finish_result(result: dict[str, Any], format: str, root: Path,
415
435
 
416
436
  8.5: the host asked the user which presentation and passes the answer
417
437
  here. One findings body per call (D8): json carries the report dict
418
- and no rendering; chat and markdown carry the rendered Markdown (on
419
- the wire the two are the same text, ADR 011 §2); html adds the HTML
420
- text beside that Markdown, returned for the host to save or show —
421
- files are the CLI's job (ADR 011 §4).
438
+ and no rendering; html adds the HTML text beside a Markdown payload,
439
+ returned for the host to save or show — files are the CLI's job
440
+ (ADR 011 §4).
441
+
442
+ **`chat` and `markdown` are not the same text on the wire**, and
443
+ this docstring said they were while the code beside it already
444
+ passed `complete=(format == "markdown")`. They are two skins:
445
+
446
+ - **chat** is the *bounded* skin — the inline UI view, trimmed to
447
+ stay under a host's payload cap so a large repository's response
448
+ is not truncated mid-report with the remediation prompt inside it;
449
+ - **markdown** is the *complete* report, the whole document, because
450
+ it was chosen as a file to keep.
451
+
452
+ A reader who believed the old sentence would have concluded the
453
+ bounding was a rendering accident rather than the contract.
422
454
  """
423
455
  if format not in ("chat", "markdown", "html", "json"):
424
456
  raise InvalidAuditArgument(f"format must be chat, markdown, html or json, not {format!r}")
@@ -155,6 +155,13 @@ CARRIED = (
155
155
  "findings_by_severity",
156
156
  "reported_not_scored",
157
157
  "loc_scanned",
158
+ #: v2 of the contract. The producer's identifier for its scoring
159
+ #: model, which is what a delegated trend is keyed on (D155). Absent
160
+ #: on a v1 document, and `None` there rather than invented — the
161
+ #: history falls back to the producer version, which fragments
162
+ #: pre-field releases correctly instead of asserting they shared one
163
+ #: model.
164
+ "scoring_model",
158
165
  )
159
166
 
160
167
 
@@ -76,6 +76,15 @@ COMPARABILITY_FIELDS: tuple[str, ...] = (
76
76
  "analyzers",
77
77
  "scored_languages",
78
78
  "scope",
79
+ # A delegated pillar's condition is stored in `pillars` like any
80
+ # other, but this tool did not measure it and cannot see when the
81
+ # producer's scoring changed. `secure-code-agent` altered its
82
+ # normalizer twice in one day without touching the document's
83
+ # schema: same shape, same fields, a different number for the same
84
+ # repository. Without this field the series joins straight across
85
+ # that -- this module's opening defect, arriving through a tool we
86
+ # delegated to rather than one we run.
87
+ "delegated_producers",
79
88
  )
80
89
 
81
90
 
@@ -131,6 +140,11 @@ class ScanRecord:
131
140
  aspects: dict[str, float | None] = field(default_factory=dict)
132
141
  pillars: dict[str, float | None] = field(default_factory=dict)
133
142
  practice_level: int | None = None
143
+ #: Each delegated pillar's producer and version, as the document
144
+ #: announced them -- `("security:secure-code-agent 0.9.0",)`. A
145
+ #: comparability field: the producer's version is the only thing
146
+ #: this tool can see that moves when their scoring does.
147
+ delegated_producers: tuple[str, ...] = ()
134
148
  evidence_status: str = ""
135
149
  # Which of those a remediation prompt actually asked somebody to fix.
136
150
  # This is what makes recurrence a strong signal rather than a weak
@@ -210,6 +224,7 @@ FIELD_NAMES: dict[str, str] = {
210
224
  "analyzers": "which analyzers contributed",
211
225
  "scored_languages": "the scored languages",
212
226
  "scope": "the scan scope",
227
+ "delegated_producers": "a delegated pillar's producer or its version",
213
228
  }
214
229
 
215
230
 
@@ -349,11 +364,31 @@ def _is_scan_record(line: str) -> bool:
349
364
  return isinstance(record, dict) and "recorded_at" in record
350
365
 
351
366
 
352
- # Fields stored as sequences, derived from the dataclass rather than
353
- # listed by hand so a new one is handled the day it is added.
354
- _SEQUENCE_FIELDS: tuple[str, ...] = (
355
- "analyzers", "scored_languages", "fingerprints", "targeted", "identities",
356
- )
367
+ def _sequence_fields() -> tuple[str, ...]:
368
+ """Fields stored as sequences, read off the dataclass.
369
+
370
+ The constant this replaced carried the comment "derived from the
371
+ dataclass rather than listed by hand so a new one is handled the day
372
+ it is added" — and was a hand-written list of five names. The
373
+ sentence was aspirational, and it cost exactly what it promised to
374
+ prevent: `delegated_producers` was added and missed, and the
375
+ round-trip guard caught a stored record no longer comparing equal to
376
+ a freshly built one.
377
+
378
+ `from __future__ import annotations` makes every annotation a
379
+ string, so the declared type is matched textually. That is narrow on
380
+ purpose: a field is a sequence here only if it says `tuple`, and a
381
+ field that stores a sequence under some other spelling would need
382
+ naming — which is a smaller trap than a list nobody updates.
383
+ """
384
+ from dataclasses import fields
385
+ return tuple(
386
+ spec.name for spec in fields(ScanRecord)
387
+ if str(spec.type).startswith("tuple")
388
+ )
389
+
390
+
391
+ _SEQUENCE_FIELDS: tuple[str, ...] = _sequence_fields()
357
392
 
358
393
 
359
394
  def read_history(path: Path) -> list[ScanRecord]:
@@ -465,6 +500,59 @@ def _score_fields(score: dict[str, Any]) -> dict[str, Any]:
465
500
  }
466
501
 
467
502
 
503
+ def _delegated_producers(report: dict[str, Any]) -> tuple[str, ...]:
504
+ """Which tool, at which version, supplied each delegated pillar.
505
+
506
+ Sorted and joined into one string per pillar so the comparability
507
+ key stays a plain value. Only delegated pillars carry a producer;
508
+ the ones this tool measures itself are already covered by
509
+ `rubric_version`.
510
+
511
+ A pillar whose document names no version still contributes its
512
+ producer, because "the same tool, version unknown" and "a different
513
+ tool" are different facts and only the second should break a series
514
+ on its own.
515
+ """
516
+ found = []
517
+ for entry in report.get("pillars") or []:
518
+ tool = entry.get("delegated_to")
519
+ if not tool:
520
+ continue
521
+ found.append(f"{entry.get('pillar')}:{tool} {_model_key(entry)}")
522
+ return tuple(sorted(found))
523
+
524
+
525
+ def _model_key(entry: dict[str, Any]) -> str:
526
+ """What identifies the instrument behind one delegated pillar.
527
+
528
+ The producer's **scoring model** when the document declares one, and
529
+ its version otherwise.
530
+
531
+ Version was the first answer and it over-breaks: a docs-only release
532
+ of the producer opens a new series having changed no score, and a
533
+ break that fires for nothing teaches a reader to ignore the ones that
534
+ matter. `scoring_model` is the producer's own identifier for "a
535
+ repository's condition could differ here for reasons that are not the
536
+ repository", so it moves when the numbers can and not otherwise.
537
+
538
+ **A document without the field is never collapsed to a single model.**
539
+ Falling back to the version fragments those releases, which is
540
+ correct: `secure-code-agent`'s releases before the field existed do
541
+ not share one scoring model — the corroboration merge, the rank
542
+ discount and the normalizer each moved the numbers independently.
543
+ Assigning them one id would assert a comparability that was never
544
+ true, so the producer reserves model 1 and never emits it.
545
+
546
+ The v1-to-v2 transition therefore breaks the series once, where the
547
+ instrument's self-description changed. That break is honest and it
548
+ happens exactly once.
549
+ """
550
+ model = entry.get("scoring_model")
551
+ if model is not None:
552
+ return f"model {model}"
553
+ return str(entry.get("producer_version") or "unversioned")
554
+
555
+
468
556
  def record_of(report: dict[str, Any], config: dict[str, Any], version: str,
469
557
  calibration: float, fingerprints: tuple[str, ...],
470
558
  targeted: tuple[str, ...] = (), backfilled: bool = False) -> ScanRecord:
@@ -496,6 +584,7 @@ def record_of(report: dict[str, Any], config: dict[str, Any], version: str,
496
584
  # opts in by setting one field rather than by threading a new
497
585
  # argument through every caller of this function.
498
586
  transformation=str(report.get("transformation") or ""),
587
+ delegated_producers=_delegated_producers(report),
499
588
  estimate=published["estimate"],
500
589
  range_low=published["range_low"],
501
590
  range_high=published["range_high"],
@@ -27,8 +27,10 @@ import shlex
27
27
  from pathlib import Path
28
28
  from typing import Any
29
29
 
30
+ from ._catalog import MAX_TIMEOUT_SECONDS, MIN_TIMEOUT_SECONDS
30
31
  from ._operator_reads import PathNotAllowed, read_source_file
31
32
  from ._runner import Invocation, run
33
+ from ._user_config import user_config_answers
32
34
  from ._xml import AnalyzerXmlRefused, parse_analyzer_xml
33
35
 
34
36
  _ENV_ASSIGNMENT = re.compile(r"^[A-Za-z_]\w*=")
@@ -67,23 +69,67 @@ def _parse_command(command: list[str]) -> tuple[dict[str, str], list[str]]:
67
69
  def suite_opted_in(config: dict[str, Any]) -> bool:
68
70
  """Both halves of the opt-in: the request, and a command to run.
69
71
 
70
- A pure function of the config it is handed, deliberately. The
71
- repository can no longer put either half there — `load_config`
72
- strips `test_execution.requested` and `expected_commands.test` out
73
- of the repository tier before the merge (D147) — so by the time a
74
- config reaches here, an opt-in in it came from the person.
75
-
76
- Keeping the check here and the boundary there is the design. A
77
- reader of this function should not have to know which tier a key
78
- came from, and a caller handing it a config should get an answer
79
- about *that* config. The tier question belongs in the one function
80
- whose job is deciding which tier wins.
72
+ A pure function of the config it is handed, deliberately.
73
+ `load_config` strips **`test_execution.requested`** out of the
74
+ repository tier before the merge (D147), so the *request* reaching
75
+ here came from the person.
76
+
77
+ `expected_commands.test` is **not** stripped, and this docstring
78
+ used to say it was. That key is documentation — "this is how you
79
+ test me" — and `require_test_command` reads it, so removing it would
80
+ fail a real gate on every repository that correctly documents its
81
+ command. The consequence is that the command in a merged config may
82
+ be the tree's, which makes this function the wrong place to learn
83
+ *what* to run. It answers only whether a run was consented to and
84
+ whether any command is documented; `opted_in_command` answers which
85
+ program the person actually agreed to.
81
86
  """
82
87
  requested = bool((config.get("test_execution") or {}).get("requested"))
83
88
  command = (config.get("expected_commands") or {}).get("test")
84
89
  return requested and bool(command)
85
90
 
86
91
 
92
+ def opted_in_command() -> list[str]:
93
+ """The test command the person consented to — user tier only.
94
+
95
+ Cited by `config._host_authority_stripped` and by `_setup_persist`
96
+ as the reader that makes keeping `expected_commands.test` in the
97
+ repository document safe. **It did not exist.** Three docstrings
98
+ described a boundary that nothing enforced, and `run_test_suite`
99
+ read the merged config instead — so a repository that documented a
100
+ command had that command executed the moment any person opted in,
101
+ which is the authority inversion D147 named and did not close.
102
+
103
+ The distinction it restores: the tree may *say* how it is tested;
104
+ only the person may choose what this host runs. Setup writes the
105
+ command into the user tier at the moment it is shown and accepted
106
+ (`_setup_persist`), so consent and program are recorded together.
107
+
108
+ Empty means the person opted in without a command reaching their
109
+ tier, which is not permission to fall back to the repository's.
110
+ """
111
+ answers = user_config_answers() or {}
112
+ command = (answers.get("expected_commands") or {}).get("test")
113
+ return list(command) if isinstance(command, list) else []
114
+
115
+
116
+ def _bounded_suite_timeout(configured: Any) -> int:
117
+ """A suite timeout the audited tree cannot weaponise.
118
+
119
+ The same clamp `_catalog._bounded_timeout` applies to
120
+ `analyzers.timeout_seconds`, on the sibling door: a crafted
121
+ `2147483647` makes the host wait sixty-eight years for a child it
122
+ was told to run, which is D40's family in a field that was missing
123
+ the bound. A non-integer or out-of-band value falls back to the
124
+ default rather than propagating.
125
+ """
126
+ try:
127
+ seconds = int(configured)
128
+ except (TypeError, ValueError):
129
+ return DEFAULT_SUITE_TIMEOUT_SECONDS
130
+ return max(MIN_TIMEOUT_SECONDS, min(seconds, MAX_TIMEOUT_SECONDS))
131
+
132
+
87
133
  def run_test_suite(root: Path, config: dict[str, Any]) -> dict[str, Any] | None:
88
134
  """Execute the configured test command, or ``None`` when not opted in.
89
135
 
@@ -92,14 +138,34 @@ def run_test_suite(root: Path, config: dict[str, Any]) -> dict[str, Any] | None:
92
138
  """
93
139
  if not suite_opted_in(config):
94
140
  return None
95
- command = list(config["expected_commands"]["test"])
141
+ # Every spawn setting is read from the **person's** tier, never from
142
+ # the merged config. `config["expected_commands"]["test"]` is what
143
+ # this line used to be, and the repository's document reaches that
144
+ # key by design (it is documentation `require_test_command` needs),
145
+ # so reading it here let the tree choose the program the moment
146
+ # anybody opted in. The keys are named here rather than hidden
147
+ # behind the helper so a reader sees which population is governed.
148
+ answers = user_config_answers() or {}
149
+ command = opted_in_command()
150
+ if not command:
151
+ return {
152
+ "command": (config.get("expected_commands") or {}).get("test") or [],
153
+ "ran": False, "exit_code": None, "passed": False,
154
+ "detail": (
155
+ "opted in, but no test command is recorded in the user tier; "
156
+ "the repository's documented command is not run on its own say-so"
157
+ ),
158
+ "coverage_percent": None,
159
+ }
96
160
  env, argv = _parse_command(command)
97
161
  # A whole test suite is legitimately slower than a single analyzer, so
98
162
  # it gets its own timeout rather than the 120s analyzer cap that was
99
163
  # killing real suites mid-run. Operator-configurable under
100
- # `test_execution.timeout_seconds`.
101
- timeout = int((config.get("test_execution") or {}).get(
102
- "timeout_seconds", DEFAULT_SUITE_TIMEOUT_SECONDS))
164
+ # `test_execution.timeout_seconds`, and clamped: a repository naming
165
+ # a timeout is naming how long this host waits.
166
+ timeout = _bounded_suite_timeout(
167
+ (answers.get("test_execution") or {}).get(
168
+ "timeout_seconds", DEFAULT_SUITE_TIMEOUT_SECONDS))
103
169
  if not argv:
104
170
  # A command that is only env assignments (or empty) names no
105
171
  # program to run. Report it as configured but unrunnable rather