mutate4py 0.2.0__tar.gz → 0.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 (427) hide show
  1. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/roles/QA.md +0 -6
  2. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/roles/hardender.md +8 -9
  3. mutate4py-0.3.0/.github/workflows/ci.yml +61 -0
  4. {mutate4py-0.2.0 → mutate4py-0.3.0}/AGENTS.md +6 -4
  5. mutate4py-0.3.0/CHANGELOG.md +52 -0
  6. {mutate4py-0.2.0 → mutate4py-0.3.0}/CONTEXT.md +55 -49
  7. mutate4py-0.3.0/PKG-INFO +404 -0
  8. mutate4py-0.3.0/README.md +388 -0
  9. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/run_acceptance.sh +8 -74
  10. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/steps/cli_surface_helpers.py +54 -73
  11. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/steps/cli_surface_steps.py +48 -51
  12. mutate4py-0.3.0/acceptance/steps/coverage_helpers.py +104 -0
  13. mutate4py-0.3.0/acceptance/steps/coverage_steps.py +190 -0
  14. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/steps/manifest_steps.py +50 -0
  15. mutate4py-0.3.0/acceptance/steps/parallel_workers_helpers.py +102 -0
  16. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/steps/parallel_workers_steps.py +141 -204
  17. mutate4py-0.3.0/acceptance/steps/run_loop_helpers.py +81 -0
  18. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/steps/run_loop_steps.py +65 -181
  19. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/steps/site_discovery_steps.py +79 -0
  20. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/steps/step_lib.py +0 -32
  21. mutate4py-0.3.0/acceptance/steps/test_selection_steps.py +392 -0
  22. mutate4py-0.3.0/acceptance/steps/tests_digest_helpers.py +124 -0
  23. mutate4py-0.3.0/acceptance/steps/tests_digest_steps.py +206 -0
  24. {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0007-coverage-default-path-and-line-only-gate.md +9 -1
  25. {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0008-coverage-flags-pairwise-exclusive.md +10 -1
  26. mutate4py-0.3.0/docs/adr/0011-timeout-printed-verbatim-folded-into-killed.md +46 -0
  27. {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0018-test-selection-three-case-no-silent-fallback.md +56 -1
  28. {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0020-single-execution-model-and-plugin-neutralisation.md +19 -0
  29. {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0021-test-context-db-isolated-session-build.md +1 -1
  30. {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0022-test-context-cache-sweeps-the-whole-project-tree.md +1 -1
  31. {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0023-unit-component-integration-test-split.md +23 -0
  32. {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0024-unary-not-and-minus-sites.md +5 -0
  33. {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0026-survivors-fail-the-run-and-stay-in-the-manifest.md +31 -3
  34. mutate4py-0.3.0/docs/adr/0027-single-session-ctrace-measurement-unit-only-scoring.md +71 -0
  35. mutate4py-0.3.0/docs/adr/0028-fork-server-harness-mode-and-swap-mode.md +70 -0
  36. mutate4py-0.3.0/docs/adr/0029-per-function-tests-digest-and-skip-rule.md +160 -0
  37. mutate4py-0.3.0/docs/demo/111-demo1-fresh-worktree-loop.txt +153 -0
  38. mutate4py-0.3.0/docs/demo/111-demo2-v0.2.0-just-mutate-cold.txt +130 -0
  39. {mutate4py-0.2.0 → mutate4py-0.3.0}/features/cli-surface.feature +57 -73
  40. mutate4py-0.3.0/features/coverage.feature +118 -0
  41. {mutate4py-0.2.0 → mutate4py-0.3.0}/features/manifest.feature +14 -0
  42. {mutate4py-0.2.0 → mutate4py-0.3.0}/features/parallel-workers.feature +21 -16
  43. {mutate4py-0.2.0 → mutate4py-0.3.0}/features/run-loop.feature +31 -42
  44. {mutate4py-0.2.0 → mutate4py-0.3.0}/features/site-discovery.feature +14 -0
  45. mutate4py-0.3.0/features/test-selection.feature +97 -0
  46. mutate4py-0.3.0/features/tests-digest.feature +137 -0
  47. {mutate4py-0.2.0 → mutate4py-0.3.0}/justfile +172 -120
  48. {mutate4py-0.2.0 → mutate4py-0.3.0}/pyproject.toml +7 -7
  49. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/__main__.py +19 -52
  50. mutate4py-0.3.0/src/mutate4py/__main__.py.manifest.json +54 -0
  51. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_cli_validation.py +23 -41
  52. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_cli_validation.py.manifest.json +43 -64
  53. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_cmd.py.manifest.json +19 -3
  54. mutate4py-0.3.0/src/mutate4py/_code_swap.py +254 -0
  55. mutate4py-0.3.0/src/mutate4py/_code_swap.py.manifest.json +309 -0
  56. mutate4py-0.3.0/src/mutate4py/_digest_changes.py +24 -0
  57. mutate4py-0.3.0/src/mutate4py/_digest_changes.py.manifest.json +39 -0
  58. mutate4py-0.3.0/src/mutate4py/_digest_inputs.py +58 -0
  59. mutate4py-0.3.0/src/mutate4py/_digest_inputs.py.manifest.json +43 -0
  60. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_dispatch.py +88 -137
  61. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_dispatch.py.manifest.json +50 -57
  62. mutate4py-0.3.0/src/mutate4py/_execution.py +238 -0
  63. mutate4py-0.3.0/src/mutate4py/_execution.py.manifest.json +36 -0
  64. mutate4py-0.3.0/src/mutate4py/_executor.py +140 -0
  65. mutate4py-0.3.0/src/mutate4py/_executor.py.manifest.json +106 -0
  66. mutate4py-0.3.0/src/mutate4py/_executor_selection.py +118 -0
  67. mutate4py-0.3.0/src/mutate4py/_executor_selection.py.manifest.json +36 -0
  68. mutate4py-0.3.0/src/mutate4py/_exit_codes.py.manifest.json +54 -0
  69. mutate4py-0.3.0/src/mutate4py/_fork_child.py +117 -0
  70. mutate4py-0.3.0/src/mutate4py/_fork_child.py.manifest.json +106 -0
  71. mutate4py-0.3.0/src/mutate4py/_forking_executor.py +351 -0
  72. mutate4py-0.3.0/src/mutate4py/_forking_executor.py.manifest.json +338 -0
  73. mutate4py-0.3.0/src/mutate4py/_fresh_digests.py +38 -0
  74. mutate4py-0.3.0/src/mutate4py/_fresh_digests.py.manifest.json +29 -0
  75. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_manifest.py +71 -33
  76. mutate4py-0.3.0/src/mutate4py/_manifest.py.manifest.json +384 -0
  77. mutate4py-0.3.0/src/mutate4py/_measure_plugin.py +80 -0
  78. mutate4py-0.3.0/src/mutate4py/_measure_plugin.py.manifest.json +71 -0
  79. mutate4py-0.3.0/src/mutate4py/_measure_record.py +196 -0
  80. mutate4py-0.3.0/src/mutate4py/_measure_record.py.manifest.json +92 -0
  81. mutate4py-0.3.0/src/mutate4py/_measurement.py +362 -0
  82. mutate4py-0.3.0/src/mutate4py/_measurement.py.manifest.json +204 -0
  83. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_plugin_neutralisation.py +16 -1
  84. mutate4py-0.3.0/src/mutate4py/_plugin_neutralisation.py.manifest.json +22 -0
  85. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_py_tree.py +4 -4
  86. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_py_tree.py.manifest.json +3 -3
  87. mutate4py-0.3.0/src/mutate4py/_pytest_argv.py +79 -0
  88. mutate4py-0.3.0/src/mutate4py/_pytest_argv.py.manifest.json +29 -0
  89. mutate4py-0.3.0/src/mutate4py/_pytest_config.py +79 -0
  90. mutate4py-0.3.0/src/mutate4py/_pytest_config.py.manifest.json +43 -0
  91. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_report.py +80 -78
  92. mutate4py-0.3.0/src/mutate4py/_report.py.manifest.json +279 -0
  93. mutate4py-0.3.0/src/mutate4py/_run_request.py +33 -0
  94. mutate4py-0.3.0/src/mutate4py/_run_request.py.manifest.json +7 -0
  95. mutate4py-0.3.0/src/mutate4py/_runner.py +395 -0
  96. mutate4py-0.3.0/src/mutate4py/_runner.py.manifest.json +189 -0
  97. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_scoring.py +13 -7
  98. mutate4py-0.3.0/src/mutate4py/_scoring.py.manifest.json +159 -0
  99. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_sidecar_io.py +5 -7
  100. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_sidecar_io.py.manifest.json +11 -11
  101. mutate4py-0.3.0/src/mutate4py/_site_selection.py +46 -0
  102. mutate4py-0.3.0/src/mutate4py/_site_selection.py.manifest.json +69 -0
  103. mutate4py-0.3.0/src/mutate4py/_skip_facts.py +83 -0
  104. mutate4py-0.3.0/src/mutate4py/_skip_facts.py.manifest.json +29 -0
  105. mutate4py-0.3.0/src/mutate4py/_skipped_run.py +50 -0
  106. mutate4py-0.3.0/src/mutate4py/_skipped_run.py.manifest.json +22 -0
  107. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_source_loading.py +68 -27
  108. mutate4py-0.3.0/src/mutate4py/_source_loading.py.manifest.json +114 -0
  109. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_subprocess_executor.py +6 -1
  110. mutate4py-0.3.0/src/mutate4py/_subprocess_executor.py.manifest.json +36 -0
  111. mutate4py-0.3.0/src/mutate4py/_test_dispatch.py +160 -0
  112. mutate4py-0.3.0/src/mutate4py/_test_dispatch.py.manifest.json +57 -0
  113. mutate4py-0.3.0/src/mutate4py/_tests_digest.py +69 -0
  114. mutate4py-0.3.0/src/mutate4py/_tests_digest.py.manifest.json +29 -0
  115. mutate4py-0.3.0/src/mutate4py/_tests_fingerprint.py +267 -0
  116. mutate4py-0.3.0/src/mutate4py/_tests_fingerprint.py.manifest.json +169 -0
  117. mutate4py-0.3.0/src/mutate4py/_unscored.py +20 -0
  118. mutate4py-0.3.0/src/mutate4py/_unscored.py.manifest.json +24 -0
  119. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_worker_protocol.py +84 -20
  120. mutate4py-0.3.0/src/mutate4py/_worker_protocol.py.manifest.json +134 -0
  121. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_worker_provision.py +11 -0
  122. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_worker_provision.py.manifest.json +22 -15
  123. mutate4py-0.3.0/src/mutate4py/_worker_results.py +64 -0
  124. mutate4py-0.3.0/src/mutate4py/_worker_results.py.manifest.json +43 -0
  125. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_worker_server.py +25 -6
  126. mutate4py-0.3.0/src/mutate4py/_worker_server.py.manifest.json +50 -0
  127. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_workers.py +117 -103
  128. mutate4py-0.3.0/src/mutate4py/_workers.py.manifest.json +85 -0
  129. {mutate4py-0.2.0 → mutate4py-0.3.0}/tach.toml +92 -23
  130. mutate4py-0.3.0/tests/_digest_project.py +62 -0
  131. mutate4py-0.3.0/tests/_dispatch_helpers.py +28 -0
  132. mutate4py-0.3.0/tests/_measurement_helpers.py +69 -0
  133. mutate4py-0.3.0/tests/_worker_fakes_helpers.py +39 -0
  134. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/conftest.py +5 -3
  135. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/polls/models.py +2 -2
  136. mutate4py-0.3.0/tests/fixtures/django_project/tests/test_polls.py +32 -0
  137. mutate4py-0.3.0/tests/fixtures/measure_plain/calc.py +15 -0
  138. mutate4py-0.3.0/tests/fixtures/measure_plain/tests/conftest.py +9 -0
  139. mutate4py-0.3.0/tests/fixtures/measure_plain/tests/helpers.py +2 -0
  140. mutate4py-0.3.0/tests/fixtures/measure_plain/tests/test_calc.py +15 -0
  141. mutate4py-0.3.0/tests/fixtures/measure_plain/tests/test_slow.py +6 -0
  142. mutate4py-0.3.0/tests/fixtures/plain_project/pytest.ini +3 -0
  143. mutate4py-0.3.0/tests/test_baseline.py +96 -0
  144. mutate4py-0.3.0/tests/test_batch_time.py +98 -0
  145. mutate4py-0.3.0/tests/test_ci_workflow_tools.py +71 -0
  146. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_cli_surface_helpers.py +95 -52
  147. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_cli_validation.py +39 -136
  148. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_cmd.py +7 -4
  149. mutate4py-0.3.0/tests/test_code_swap.py +262 -0
  150. mutate4py-0.3.0/tests/test_coverage_helpers.py +160 -0
  151. mutate4py-0.3.0/tests/test_digest_inputs.py +82 -0
  152. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_discovery.py +58 -3
  153. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_dispatch.py +34 -466
  154. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_dispatch_cli.py +21 -7
  155. mutate4py-0.3.0/tests/test_dispatch_measurement.py +88 -0
  156. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_django_workers_integration.py +28 -33
  157. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_env_overlay_integration.py +0 -12
  158. mutate4py-0.3.0/tests/test_execution.py +480 -0
  159. mutate4py-0.3.0/tests/test_executor_composition.py +487 -0
  160. mutate4py-0.3.0/tests/test_executor_description.py +68 -0
  161. mutate4py-0.3.0/tests/test_executor_selection.py +217 -0
  162. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_exit_codes.py +12 -1
  163. mutate4py-0.3.0/tests/test_fork_child.py +162 -0
  164. mutate4py-0.3.0/tests/test_forking_executor.py +596 -0
  165. mutate4py-0.3.0/tests/test_fresh_digests.py +83 -0
  166. mutate4py-0.3.0/tests/test_install_tools.py +187 -0
  167. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_integration_marker_guard.py +1 -1
  168. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_main.py +38 -130
  169. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_main_cli_flags.py +7 -66
  170. mutate4py-0.3.0/tests/test_manifest_fingerprint.py +232 -0
  171. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_manifest_modes.py +55 -0
  172. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_manifest_score.py +226 -0
  173. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_manifest_storage.py +27 -0
  174. mutate4py-0.3.0/tests/test_measure_record.py +147 -0
  175. mutate4py-0.3.0/tests/test_measurement.py +137 -0
  176. mutate4py-0.3.0/tests/test_measurement_gate.py +279 -0
  177. mutate4py-0.3.0/tests/test_measurement_index.py +284 -0
  178. mutate4py-0.3.0/tests/test_mutate_recipe_report.py +85 -0
  179. mutate4py-0.3.0/tests/test_mutate_unit_only.py +227 -0
  180. mutate4py-0.3.0/tests/test_no_change_rerun.py +169 -0
  181. mutate4py-0.3.0/tests/test_parallel_workers_helpers.py +156 -0
  182. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_plugin_neutralisation.py +15 -0
  183. mutate4py-0.3.0/tests/test_pytest_argv.py +43 -0
  184. mutate4py-0.3.0/tests/test_pytest_config.py +106 -0
  185. mutate4py-0.3.0/tests/test_removed_coverage_flags.py +74 -0
  186. mutate4py-0.3.0/tests/test_removed_selection_flags.py +78 -0
  187. mutate4py-0.3.0/tests/test_removed_test_context_flags.py +84 -0
  188. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_report.py +51 -32
  189. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_run_acceptance_script.py +1 -0
  190. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_run_idempotence.py +7 -17
  191. mutate4py-0.3.0/tests/test_run_loop_helpers.py +151 -0
  192. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_run_mutations_modes.py +54 -263
  193. mutate4py-0.3.0/tests/test_run_tests_digest.py +407 -0
  194. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_runner.py +17 -137
  195. mutate4py-0.3.0/tests/test_runner_measurement.py +280 -0
  196. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_scoring.py +50 -0
  197. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_site_selection.py +11 -60
  198. mutate4py-0.3.0/tests/test_skip_facts.py +175 -0
  199. mutate4py-0.3.0/tests/test_skipped_run.py +88 -0
  200. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_source_loading.py +37 -0
  201. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_subprocess_executor.py +6 -0
  202. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_survivor_gate.py +149 -82
  203. mutate4py-0.3.0/tests/test_test_dispatch.py +183 -0
  204. mutate4py-0.3.0/tests/test_tests_digest.py +181 -0
  205. mutate4py-0.3.0/tests/test_tests_digest_helpers.py +93 -0
  206. mutate4py-0.3.0/tests/test_tests_fingerprint.py +404 -0
  207. mutate4py-0.3.0/tests/test_tool_preflight.py +103 -0
  208. mutate4py-0.3.0/tests/test_unscored.py +64 -0
  209. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_worker_protocol.py +149 -28
  210. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_worker_server.py +35 -0
  211. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_workers.py +92 -328
  212. mutate4py-0.3.0/tests/test_workers_dispatch.py +351 -0
  213. {mutate4py-0.2.0 → mutate4py-0.3.0}/uv.lock +29 -1
  214. mutate4py-0.2.0/.github/workflows/ci.yml +0 -96
  215. mutate4py-0.2.0/PKG-INFO +0 -310
  216. mutate4py-0.2.0/README.md +0 -295
  217. mutate4py-0.2.0/acceptance/steps/cli_surface_qa_steps.py +0 -306
  218. mutate4py-0.2.0/acceptance/steps/coverage_helpers.py +0 -187
  219. mutate4py-0.2.0/acceptance/steps/coverage_qa_steps.py +0 -266
  220. mutate4py-0.2.0/acceptance/steps/coverage_steps.py +0 -206
  221. mutate4py-0.2.0/acceptance/steps/manifest_qa_steps.py +0 -247
  222. mutate4py-0.2.0/acceptance/steps/parallel_workers_qa_helpers.py +0 -105
  223. mutate4py-0.2.0/acceptance/steps/parallel_workers_qa_steps.py +0 -561
  224. mutate4py-0.2.0/acceptance/steps/run_loop_helpers.py +0 -86
  225. mutate4py-0.2.0/acceptance/steps/run_loop_qa_helpers.py +0 -94
  226. mutate4py-0.2.0/acceptance/steps/run_loop_qa_steps.py +0 -351
  227. mutate4py-0.2.0/acceptance/steps/site_discovery_qa_steps.py +0 -198
  228. mutate4py-0.2.0/acceptance/steps/test_selection_steps.py +0 -505
  229. mutate4py-0.2.0/docs/adr/0011-timeout-printed-verbatim-folded-into-killed.md +0 -28
  230. mutate4py-0.2.0/features/cli-surface_qa.feature +0 -126
  231. mutate4py-0.2.0/features/coverage.feature +0 -170
  232. mutate4py-0.2.0/features/coverage_qa.feature +0 -143
  233. mutate4py-0.2.0/features/manifest_qa.feature +0 -100
  234. mutate4py-0.2.0/features/parallel-workers_qa.feature +0 -152
  235. mutate4py-0.2.0/features/run-loop_qa.feature +0 -142
  236. mutate4py-0.2.0/features/site-discovery_qa.feature +0 -97
  237. mutate4py-0.2.0/features/test-selection.feature +0 -121
  238. mutate4py-0.2.0/scripts/check_context_deselection.py +0 -194
  239. mutate4py-0.2.0/src/mutate4py/__main__.py.manifest.json +0 -29
  240. mutate4py-0.2.0/src/mutate4py/_baseline.py +0 -115
  241. mutate4py-0.2.0/src/mutate4py/_baseline.py.manifest.json +0 -57
  242. mutate4py-0.2.0/src/mutate4py/_coverage.py +0 -116
  243. mutate4py-0.2.0/src/mutate4py/_coverage.py.manifest.json +0 -57
  244. mutate4py-0.2.0/src/mutate4py/_execution.py +0 -152
  245. mutate4py-0.2.0/src/mutate4py/_execution.py.manifest.json +0 -29
  246. mutate4py-0.2.0/src/mutate4py/_executor.py +0 -19
  247. mutate4py-0.2.0/src/mutate4py/_executor.py.manifest.json +0 -22
  248. mutate4py-0.2.0/src/mutate4py/_executor_selection.py +0 -66
  249. mutate4py-0.2.0/src/mutate4py/_executor_selection.py.manifest.json +0 -22
  250. mutate4py-0.2.0/src/mutate4py/_exit_codes.py.manifest.json +0 -29
  251. mutate4py-0.2.0/src/mutate4py/_forking_executor.py +0 -298
  252. mutate4py-0.2.0/src/mutate4py/_forking_executor.py.manifest.json +0 -99
  253. mutate4py-0.2.0/src/mutate4py/_manifest.py.manifest.json +0 -183
  254. mutate4py-0.2.0/src/mutate4py/_plugin_neutralisation.py.manifest.json +0 -15
  255. mutate4py-0.2.0/src/mutate4py/_report.py.manifest.json +0 -106
  256. mutate4py-0.2.0/src/mutate4py/_runner.py +0 -382
  257. mutate4py-0.2.0/src/mutate4py/_runner.py.manifest.json +0 -71
  258. mutate4py-0.2.0/src/mutate4py/_scoring.py.manifest.json +0 -78
  259. mutate4py-0.2.0/src/mutate4py/_site_selection.py +0 -92
  260. mutate4py-0.2.0/src/mutate4py/_site_selection.py.manifest.json +0 -57
  261. mutate4py-0.2.0/src/mutate4py/_source_loading.py.manifest.json +0 -43
  262. mutate4py-0.2.0/src/mutate4py/_subprocess_executor.py.manifest.json +0 -29
  263. mutate4py-0.2.0/src/mutate4py/_test_collection.py +0 -120
  264. mutate4py-0.2.0/src/mutate4py/_test_collection.py.manifest.json +0 -57
  265. mutate4py-0.2.0/src/mutate4py/_test_context_build.py +0 -133
  266. mutate4py-0.2.0/src/mutate4py/_test_context_build.py.manifest.json +0 -43
  267. mutate4py-0.2.0/src/mutate4py/_test_context_cache.py +0 -229
  268. mutate4py-0.2.0/src/mutate4py/_test_context_cache.py.manifest.json +0 -127
  269. mutate4py-0.2.0/src/mutate4py/_test_context_orchestration.py +0 -75
  270. mutate4py-0.2.0/src/mutate4py/_test_context_orchestration.py.manifest.json +0 -15
  271. mutate4py-0.2.0/src/mutate4py/_test_dispatch.py +0 -99
  272. mutate4py-0.2.0/src/mutate4py/_test_dispatch.py.manifest.json +0 -29
  273. mutate4py-0.2.0/src/mutate4py/_test_selection.py +0 -182
  274. mutate4py-0.2.0/src/mutate4py/_test_selection.py.manifest.json +0 -78
  275. mutate4py-0.2.0/src/mutate4py/_worker_protocol.py.manifest.json +0 -99
  276. mutate4py-0.2.0/src/mutate4py/_worker_server.py.manifest.json +0 -43
  277. mutate4py-0.2.0/src/mutate4py/_workers.py.manifest.json +0 -99
  278. mutate4py-0.2.0/tests/_coverage_db_helpers.py +0 -28
  279. mutate4py-0.2.0/tests/_fork_unsafe_plugin_helpers.py +0 -23
  280. mutate4py-0.2.0/tests/fixtures/django_project/tests/test_polls.py +0 -20
  281. mutate4py-0.2.0/tests/fixtures/overlapping_coverage/.coveragerc +0 -2
  282. mutate4py-0.2.0/tests/fixtures/overlapping_coverage/shared.py +0 -10
  283. mutate4py-0.2.0/tests/fixtures/overlapping_coverage/test_a.py +0 -6
  284. mutate4py-0.2.0/tests/fixtures/overlapping_coverage/test_b.py +0 -6
  285. mutate4py-0.2.0/tests/test_baseline.py +0 -256
  286. mutate4py-0.2.0/tests/test_check_context_deselection.py +0 -277
  287. mutate4py-0.2.0/tests/test_coverage.py +0 -394
  288. mutate4py-0.2.0/tests/test_coverage_helpers.py +0 -330
  289. mutate4py-0.2.0/tests/test_execution.py +0 -275
  290. mutate4py-0.2.0/tests/test_executor_composition.py +0 -357
  291. mutate4py-0.2.0/tests/test_executor_selection.py +0 -163
  292. mutate4py-0.2.0/tests/test_forking_executor.py +0 -587
  293. mutate4py-0.2.0/tests/test_parallel_workers_qa_helpers.py +0 -143
  294. mutate4py-0.2.0/tests/test_run_loop_qa_helpers.py +0 -134
  295. mutate4py-0.2.0/tests/test_test_collection.py +0 -139
  296. mutate4py-0.2.0/tests/test_test_context_build.py +0 -301
  297. mutate4py-0.2.0/tests/test_test_context_cache.py +0 -510
  298. mutate4py-0.2.0/tests/test_test_context_orchestration.py +0 -261
  299. mutate4py-0.2.0/tests/test_test_selection.py +0 -639
  300. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/backlog.md +0 -0
  301. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/ledger-project.md +0 -0
  302. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/ledger-swarmforge.md +0 -0
  303. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/references/equivalent-mutants.md +0 -0
  304. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/roles/cleaner.md +0 -0
  305. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/roles/coder.md +0 -0
  306. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/agent-retro/SKILL.md +0 -0
  307. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/agent-retro/scripts/extract.py +0 -0
  308. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/domain-modeling/ADR-FORMAT.md +0 -0
  309. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/domain-modeling/CONTEXT-FORMAT.md +0 -0
  310. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/domain-modeling/SKILL.md +0 -0
  311. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/explain/SKILL.md +0 -0
  312. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/grill-with-docs/SKILL.md +0 -0
  313. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/grilling/SKILL.md +0 -0
  314. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/recall/SKILL.md +0 -0
  315. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/replay/SKILL.md +0 -0
  316. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/review/SKILL.md +0 -0
  317. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/review/references/review-rules.md +0 -0
  318. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/search/SKILL.md +0 -0
  319. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/session-crosslink/SKILL.md +0 -0
  320. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/session-handoff/SKILL.md +0 -0
  321. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/session-to-skill/SKILL.md +0 -0
  322. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/setup-swarm/SKILL.md +0 -0
  323. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/teach/SKILL.md +0 -0
  324. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/using-entire/SKILL.md +0 -0
  325. {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/what-happened/SKILL.md +0 -0
  326. {mutate4py-0.2.0 → mutate4py-0.3.0}/.claude/agents/entire-search.md +0 -0
  327. {mutate4py-0.2.0 → mutate4py-0.3.0}/.claude/settings.json +0 -0
  328. {mutate4py-0.2.0 → mutate4py-0.3.0}/.entire/.gitignore +0 -0
  329. {mutate4py-0.2.0 → mutate4py-0.3.0}/.entire/settings.json +0 -0
  330. {mutate4py-0.2.0 → mutate4py-0.3.0}/.github/workflows/release.yml +0 -0
  331. {mutate4py-0.2.0 → mutate4py-0.3.0}/.gitignore +0 -0
  332. {mutate4py-0.2.0 → mutate4py-0.3.0}/.python-version +0 -0
  333. {mutate4py-0.2.0 → mutate4py-0.3.0}/CLAUDE.md +0 -0
  334. {mutate4py-0.2.0 → mutate4py-0.3.0}/LICENSE +0 -0
  335. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/__init__.py +0 -0
  336. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/fixtures/empty_units.py +0 -0
  337. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/fixtures/mixed_operators.py +0 -0
  338. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/fixtures/module_level.py +0 -0
  339. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/fixtures/plain.py +0 -0
  340. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/fixtures/stale.py +0 -0
  341. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/fixtures/unary_operators.py +0 -0
  342. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/generate_acceptance.py +0 -0
  343. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/run_gherkin_mutation.sh +0 -0
  344. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/runner_adapter.py +0 -0
  345. {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/steps/__init__.py +0 -0
  346. {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0001-site-discovery-and-operators.md +0 -0
  347. {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0005-manifest-hash-is-ast-unparse.md +0 -0
  348. {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0006-update-manifest-idempotent.md +0 -0
  349. {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0015-parallel-workers-via-uv-clone-per-worker.md +0 -0
  350. {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0017-multi-root-targets-and-uv-workspace-autodiscovery.md +0 -0
  351. {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0025-scored-runs-refuse-a-foreign-interpreter.md +0 -0
  352. {mutate4py-0.2.0 → mutate4py-0.3.0}/features/ci.feature +0 -0
  353. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/__init__.py +0 -0
  354. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/__init__.py.manifest.json +0 -0
  355. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_bytecode_cache.py +0 -0
  356. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_bytecode_cache.py.manifest.json +0 -0
  357. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_cmd.py +0 -0
  358. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_discovery.py +0 -0
  359. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_discovery.py.manifest.json +0 -0
  360. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_django_worker_plugin.py +0 -0
  361. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_django_worker_plugin.py.manifest.json +0 -0
  362. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_equivalent.py +0 -0
  363. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_equivalent.py.manifest.json +0 -0
  364. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_exit_codes.py +0 -0
  365. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_glob_dialect.py +0 -0
  366. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_glob_dialect.py.manifest.json +0 -0
  367. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_ids.py +0 -0
  368. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_ids.py.manifest.json +0 -0
  369. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_manifest_modes.py +0 -0
  370. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_manifest_modes.py.manifest.json +0 -0
  371. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_manifest_storage.py +0 -0
  372. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_manifest_storage.py.manifest.json +0 -0
  373. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_target_resolution.py +0 -0
  374. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_target_resolution.py.manifest.json +0 -0
  375. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_workspace.py +0 -0
  376. {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_workspace.py.manifest.json +0 -0
  377. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarm +0 -0
  378. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/constitution/articles/engineering.prompt +0 -0
  379. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/constitution/articles/handoffs.prompt +0 -0
  380. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/constitution/articles/local-engineering.prompt +0 -0
  381. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/constitution/articles/local-workflow.prompt +0 -0
  382. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/constitution/articles/project.prompt +0 -0
  383. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/constitution/articles/workflow.prompt +0 -0
  384. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/constitution.prompt +0 -0
  385. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/roles/QA.prompt +0 -0
  386. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/roles/architect.prompt +0 -0
  387. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/roles/cleaner.prompt +0 -0
  388. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/roles/coder.prompt +0 -0
  389. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/roles/curator.prompt +0 -0
  390. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/roles/hardender.prompt +0 -0
  391. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/roles/integrator.prompt +0 -0
  392. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/roles/specifier.prompt +0 -0
  393. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/roles/ux-engineer.prompt +0 -0
  394. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/skills/agent-retro/SKILL.md +0 -0
  395. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/skills/agent-retro/scripts/extract.py +0 -0
  396. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/skills/setup-swarm/SKILL.md +0 -0
  397. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/swarmforge.conf +0 -0
  398. {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/templates/feature.feature +0 -0
  399. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/__init__.py +0 -0
  400. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/_module_size_guard.py +0 -0
  401. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/_pytest_project_helpers.py +0 -0
  402. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/calc.py +0 -0
  403. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/polls/__init__.py +0 -0
  404. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/polls/apps.py +0 -0
  405. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/polls/migrations/0001_initial.py +0 -0
  406. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/polls/migrations/__init__.py +0 -0
  407. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/pyproject.toml +0 -0
  408. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/pytest.ini +0 -0
  409. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/settings.py +0 -0
  410. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/tests/__init__.py +0 -0
  411. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/tests/test_calc.py +0 -0
  412. {mutate4py-0.2.0/tests/fixtures/plain_project → mutate4py-0.3.0/tests/fixtures/measure_plain}/pytest.ini +0 -0
  413. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/plain_project/calc.py +0 -0
  414. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/plain_project/pyproject.toml +0 -0
  415. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/plain_project/tests/test_calc.py +0 -0
  416. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_django_worker_plugin.py +0 -0
  417. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_equivalent.py +0 -0
  418. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_glob_dialect.py +0 -0
  419. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_hash_stability.py +0 -0
  420. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_manifest.py +0 -0
  421. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_module_size_guard.py +0 -0
  422. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_properties.py +0 -0
  423. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_py_tree.py +0 -0
  424. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_sidecar_io.py +0 -0
  425. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_target_resolution.py +0 -0
  426. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_worker_provision.py +0 -0
  427. {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_workspace.py +0 -0
@@ -3,12 +3,6 @@
3
3
  ## Pre-Handoff Lint and Format Check
4
4
  Before handing off, MUST run `uv run ruff check src/ tests/ && uv run ruff format --check src/ tests/`. Fix any failures before handoff. Do not hand off with lint or format violations — integrator absorbs CI fix cycles.
5
5
 
6
- ## QA Feature Step Wiring
7
- Before marking QA done, grep `acceptance/run_acceptance.sh` for every `*_qa.feature` file present in `features/`. Confirm each has a `QA_STEPS_MAP` entry. A missing `QA_STEPS_MAP` entry causes silent `SKIP QA: no steps module for X` output — the feature is silently skipped and failures are masked.
8
-
9
- ## QA Harness: Handling CLI-Rejected Values (e.g. --max-workers 0)
10
- When a QA feature passes a value that the CLI rejects (e.g., `--max-workers 0` to test serial mode), handle it in `_run_qa_cmd` — not by changing the CLI validator. Changing the CLI validator to accept test-only values breaks the cli-surface acceptance test.
11
-
12
6
  ## Shell Counter Files in Acceptance Steps
13
7
  Shell scripts in acceptance steps must not use `$COUNT` from `ls dir | wc -l` as unique file names with parallel workers — it is not safe under concurrency. Use `$$` (PID) or content-based detection (grep for a token in the worker's source copy) instead.
14
8
 
@@ -4,25 +4,24 @@
4
4
  This project (mutate4py) is self-hosted — it generates and checks its own mutation manifests. Use `uv run mutate4py` (not `uvx mutate4py`; uvx isolates the env and breaks self-referential scanning).
5
5
 
6
6
  Key commands:
7
- - `uv run mutate4py src/ --check-manifest` — verify all manifests are current (CI gate; exits 0 if current, 1 if missing/stale)
7
+ - `uv run mutate4py src/ --check-manifest --manifest-file` — verify the manifests are current: unit hashes, recorded Survivors and equivalent markers. It runs no test, so it does not check tests digests. CI gate; exits 0 if current, 1 if a manifest is missing, stale, or records an unaccepted Survivor.
8
8
  - `uv run mutate4py <file> --scan` — scan a single file and print surviving mutants
9
9
  - `uv run mutate4py <file> --update-manifest` — regenerate and embed a file's manifest
10
10
 
11
11
  `--scan`, `--update-manifest`, and `--check-manifest` are mutually exclusive. `--scan` and `--update-manifest` accept exactly ONE file; for multi-file operations loop over files or use a directory with `--check-manifest`.
12
12
 
13
- ## Running a Scored Mutation Pass (use `just mutate`, never raw `mutate4py --mutate-all`)
14
- `_runner.py` prints one `[i/total] <status> line N ...` line per mutation site unconditionally on a scored run — for the ~346-site full-src/ surface that's ~20k chars an agent's Bash tool would otherwise capture in full, every hardening iteration.
13
+ ## Running a Scored Mutation Pass (use `just mutate`, never raw `mutate4py`)
14
+ `_runner.py` prints one `[i/total] <status> line N ...` line per mutation site unconditionally on a scored run. On a full-src/ run that is many thousands of characters an agent's Bash tool would otherwise capture in full, every hardening iteration.
15
15
 
16
- Invoke scored runs through the justfile instead of calling `mutate4py --mutate-all` directly:
16
+ Invoke scored runs through the justfile instead of calling `mutate4py` directly:
17
17
  ```
18
- just check test-unit test-integration # once per verification pass, refreshes lcov.info
19
- just mutate <path> # e.g. just mutate src/mutate4py/_runner.py --mutate-all
18
+ just mutate <path> # e.g. just mutate src/mutate4py/_runner.py
20
19
  ```
21
- `just mutate` is `justfile`'s wrapper: it redirects the run to a temp log, then prints only the `Mutation Report` block (Killed/Survived/Uncovered counts + every Survivor's `line N desc` — still fully actionable) via `awk '/^Mutation Report$/,0'`. On a non-zero exit other than 3 it also tails the last 20 log lines, so an infra failure that happens before the summary prints is still visible. This is the same shape `perf.sh`'s (now `just perf`) `_run` helper already used — see `justfile`'s header comment.
20
+ `just mutate` is `justfile`'s wrapper: it redirects the run to a temp log, then prints only the `Mutation Report` block (Killed/Survived/Uncovered counts + every Survivor's `line N desc` — still fully actionable) via `awk '/^Mutation Report$/,0'`. On a non-zero exit other than 3 it also tails the last 20 log lines, so an infra failure that happens before the summary prints is still visible.
22
21
 
23
- Exit 3 means unaccepted survivors: kill them, or mark a recognized equivalent with `# mutate4py: equivalent <category>` on the Site's line (ADR 0026).
22
+ Exit 3 means unaccepted Survivors: kill them with a unit test (ADR 0023: code mutation is unit-only, so a Mutant that only a component test kills stays a Survivor), or mark a recognized equivalent with `# mutate4py: equivalent <category>` on the Site's line (ADR 0026). A Survivor recorded on a function whose Unit hash and tests digest are unchanged is skipped by later runs and still exits 3 (ADR 0029). It is not retried, so it has to be killed or accepted rather than waited out.
24
23
 
25
- `just mutate` narrows against `contexts.db`, an isolated-session test-context db (docs/adr/0021) it builds/refreshes itself every run via its `test-context-db` dependency — no separate step needed. A cache (mutate4py's own, keyed on the project tree) makes a rerun on an unchanged tree ~4 seconds; a first build, or one after a source/test edit, costs ~16 minutes on this project's full non-integration suite. That first cost is not a hang — let it run.
24
+ The loop: a scored run re-scores the changed functions only. A changed function is one whose Unit hash or Tests digest moved since its Manifest entry was written, one that is Unscored, or one that records Survivors but has no digest yet. Each run takes one Measurement first (the unit tests, once, under coverage) and narrows each Mutant to the tests that reached its line, so there is no setup step to run before `just mutate`. An unchanged function is skipped, its recorded Survivors included, and they still fail the run. There is no flag for a full re-score: `--mutate-all` is removed (spec #115). A run with no Manifest scores every function (an uncovered one gets an `uncovered` score), so a deliberate full re-score needs a file with no sidecar Manifest.
26
25
 
27
26
  `just check` (no args) runs the full CI-equivalent gate list the same way — one ✓/✗ line per gate, full output in `check.log`, tailed only on failure.
28
27
 
@@ -0,0 +1,61 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ ci:
13
+ name: CI
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
17
+ - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
18
+ with:
19
+ enable-cache: true
20
+ version: "0.12.3"
21
+ - uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4.0.0
22
+ with:
23
+ just-version: "1.58.0"
24
+ - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
25
+ with:
26
+ go-version: "1.26.5"
27
+ # Gate commands live in the justfile (one source of truth for CI + local
28
+ # `just check`) — see justfile's header comment. Steps stay separate
29
+ # (rather than one `just check` call) so the Actions UI keeps per-gate
30
+ # timing and log folding.
31
+ - name: Lint
32
+ run: just lint
33
+ - name: Format check
34
+ run: just format-check
35
+ # Coverage is measured on test-unit alone: it writes lcov.info and
36
+ # applies the threshold, so the CRAP step below scores unit-only data.
37
+ # test-component appends to .coverage without touching lcov.info, and
38
+ # test-integration adds no coverage data at all (measured; see the
39
+ # justfile). Both stay here as test gates in their own right. These steps share one working directory by necessity: test-unit
40
+ # cannot move to a separate job from CRAP without merging coverage first.
41
+ - name: Tests (unit)
42
+ run: just test-unit
43
+ - name: Tests (component)
44
+ run: just test-component
45
+ - name: Tests (integration)
46
+ run: just test-integration
47
+ - name: CRAP
48
+ run: just crap
49
+ - name: Install tools
50
+ # Installs the pinned drywall and gherkin-parser through the justfile's
51
+ # `install-tools` recipe, the same one a local run uses, so the pins
52
+ # live in one place. Adds ~/.local/bin to PATH for the steps after it.
53
+ run: |
54
+ just install-tools
55
+ echo "$HOME/.local/bin" >> "$GITHUB_PATH"
56
+ - name: DRY
57
+ run: just dry
58
+ - name: Mutation manifest
59
+ run: just manifest
60
+ - name: Acceptance tests
61
+ run: just acceptance
@@ -8,14 +8,15 @@ Navigation and universal invariants for all agents in this project.
8
8
 
9
9
  ## Local Gates
10
10
  - `just check` — all CI gates, same order as `.github/workflows/ci.yml` (log-per-gate in `check.log`, ✓/✗ printed, tail on failure). `just check <gate> [<gate> ...]` runs a subset; `just --list` shows public recipes.
11
- - `just mutate <path> [<args>]` — scored mutation run with per-mutant progress captured to a log; only the `Mutation Report` summary is printed. Use this instead of raw `uv run mutate4py <path> --mutate-all` (see `.agents/roles/hardender.md`). Depends on `test-context-db`, so a bare `just mutate <path>` also builds-or-refreshes the isolated-session test-context db first: ~16 min on a cold cache, ~4s once warm (see the `test-context-db` recipe in `justfile`).
11
+ - `just mutate <path> [<args>]` — scored mutation run with per-mutant progress captured to a log; only the `Mutation Report` summary is printed. Use this instead of raw `uv run mutate4py <path>` (see `.agents/roles/hardender.md`).
12
+ - While iterating on a change, run `just check test-unit` (make the behaviour work first); the full `just check` runs once before handing off or committing.
12
13
 
13
14
  ## Code Review
14
15
  - Use `/mattpocock-skills:code-review`, never bare `/code-review` (different, heavier skill).
15
16
 
16
17
  ## Tool Paths (Local Machine)
17
18
  - `crap4py`: installed from local sibling `~/workspace/addi/crap4py` via `uv tool install ~/workspace/addi/crap4py` (not PyPI, not GitHub URL)
18
- - `drywall`: available at `/Users/gabadi/.local/bin/drywall` (not in PyPI under that name)
19
+ - `drywall` and `gherkin-parser` (not in PyPI): run `just install-tools` once per machine. It installs the versions pinned in the `justfile` (`DRYWALL_TAG`, `GHERKIN_PARSER_COMMIT`) into `~/.local/bin`, the same pins CI installs with, and rerunning it is a no-op. `~/.local/bin` must be on PATH. When either tool is missing, `just check` fails before its first gate and names this recipe.
19
20
  - `uv run mutate4py` is the correct invocation for mutation scan and manifest checks (not `uvx mutate4py`); uvx isolates the env and breaks self-referential scanning
20
21
  - `uv run mutate4py src/ --check-manifest --manifest-file` — CI gate; exits 0 if all manifests current, 1 if any are missing/stale. `src/` stores manifests as sidecars (`*.manifest.json`), so `--manifest-file` is required — without it the check looks for embedded footers and always fails (issue #47).
21
22
 
@@ -26,7 +27,7 @@ Navigation and universal invariants for all agents in this project.
26
27
  - `mutate4py --scan`: accepts exactly ONE file argument; loop over files separately for multi-file scans
27
28
 
28
29
  ## Acceptance Test Safety
29
- - Manifest QA fixtures (`acceptance/fixtures/plain.py`, `acceptance/fixtures/stale.py`) are committed inputs and MUST NOT be overwritten; all manifest QA steps must use a writable-copy pattern (see `acceptance/steps/manifest_qa_steps.py:setup_copy()`)
30
+ - Committed fixtures in `acceptance/fixtures/` (`plain.py`, `stale.py` for `manifest.feature`; `mixed_operators.py` and the others for `site-discovery.feature`) are committed inputs and MUST NOT be overwritten; steps that run mutate4py against one work on a writable copy (see `acceptance/steps/manifest_steps.py:setup_copy()` and `site_discovery_steps.py:given_fixture_copy()`). There is no separate QA feature pass: each `X_qa.feature` was folded into `X.feature`.
30
31
  - `runner_adapter.py` must be executable (`chmod +x`) before gherkin-mutator can use it; tool does not emit a clear permission error; supports `RUNNER_WORKERS` env var (default 4) for concurrent job execution
31
32
  - When modifying source with an embedded manifest, always strip manifest first, modify body, then re-embed; never append after the manifest footer — `strip_manifest` removes everything from begin-marker to EOF
32
33
 
@@ -35,7 +36,8 @@ Navigation and universal invariants for all agents in this project.
35
36
  - Acceptance step files are boundary files (15-site mutation threshold); extractable pure logic belongs in `*_helpers.py` modules with their own unit tests.
36
37
  - `coverage_helpers.py` is the testable module for acceptance step helper functions; unit tests live in `tests/test_coverage_helpers.py`.
37
38
  - `cli_surface_helpers.py` is the testable module for CLI surface acceptance step helpers; unit tests live in `tests/test_cli_surface_helpers.py`.
38
- - `__main__.py` is a pure CLI adapter. `_runner.py` re-exports `scan_report`, `scan_report_with_coverage` (defined in `_report.py`), `update_manifest` and `check_manifest` (defined in `_manifest_modes.py`); test them directly. Do not move domain functions back to `__main__.py`.
39
+ - `run_loop_helpers.py` and `parallel_workers_helpers.py` are the testable modules for the run-loop and parallel-workers acceptance steps; unit tests live in `tests/test_run_loop_helpers.py` and `tests/test_parallel_workers_helpers.py`.
40
+ - `__main__.py` is a pure CLI adapter. `_runner.py` re-exports `scan_report` (defined in `_report.py`), `update_manifest` and `check_manifest` (defined in `_manifest_modes.py`); test them directly. Do not move domain functions back to `__main__.py`.
39
41
 
40
42
  ## References
41
43
  - `CONTEXT.md` — the project's glossary. Use its terms; don't drift to synonyms.
@@ -0,0 +1,52 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0 (unreleased)
4
+
5
+ The run measures coverage itself and re-scores only what changed (spec #89). Decisions
6
+ are in [`docs/adr/`](docs/adr/): ADR 0027 (the Measurement), ADR 0028 (executors),
7
+ ADR 0029 (the tests digest and skip rule).
8
+
9
+ ### Removed flags
10
+
11
+ Each flag below is refused with exit 2 and a message naming it, `<flag> removed in 0.3.0: <reason>`.
12
+ Remove them from scripts and CI.
13
+
14
+ - `--cov-cmd`: the run measures coverage itself (the Measurement, ADR 0027). No replacement.
15
+ - `--lcov`: the run measures coverage itself; no `lcov.info` is read or written. No replacement.
16
+ - `--reuse-coverage`: each scored run takes its Measurement; a rerun with nothing changed skips it. No replacement.
17
+ - `--test-contexts`: selection reads the run's own Measurement and its Test map; there is no db to pass. No replacement.
18
+ - `--build-test-contexts`: no test-context db is built. No replacement.
19
+ - `--since-last-run`: a default run already re-scores only the changed functions. Drop the flag.
20
+ - `--lines`: edit a function to re-score it.
21
+ - `--mutate-all`: a run scores every changed or Unscored function itself. A run with no Manifest scores every function (an uncovered one gets an `uncovered` score). Drop the flag.
22
+
23
+ ### Changed
24
+
25
+ - Coverage comes from one Measurement per scored run: one pytest session of the unit tests
26
+ under coverage.py, which also gives the Baseline and the Test map (ADR 0027).
27
+ - New runtime dependency: coverage.py, `coverage>=7.10`.
28
+ - `--min-timeout` default is 5.0 s, up from 1.0 s. `--timeout-factor` stays at 10.
29
+ - The per-Mutant overhead is the Measurement's wall time minus the summed durations of its
30
+ tests. No `--collect-only` pass runs. The overhead hint fires at half of the Measurement's wall.
31
+ - A default run re-scores only changed functions. A function is changed when its Unit hash
32
+ or its tests digest differs, it is Unscored, or it records Survivors and has no digest yet
33
+ (ADR 0029). A default run also scores every Unscored function, including one no covered Site reaches (spec #115). A run with no Manifest scores every function (an uncovered one gets an `uncovered` score); `--mutate-all` is removed.
34
+ - Unchanged functions are skipped even when they record Survivors. Those Survivors print under
35
+ `Recorded survivors (function unchanged, not re-run):` and still count toward exit 3 (0 with `--allow-survivors`).
36
+ - Test selection has two outcomes, `narrowed` and `static`, read from the Measurement's Test map
37
+ (ADR 0018). The report prints `Test selection: narrowed <n>, static <k>`.
38
+ - Every non-accepted Survivor is re-run with the full test set, in a fresh interpreter through the
39
+ subprocess executor. It counts only if it survives that run too (ADR 0018 amendment #97, ADR 0028).
40
+ - Executors: the forking executor runs Mutants by fresh import or swap mode (ADR 0028); the
41
+ `Executor:` line of the report says how each Mutant ran.
42
+ - `--check-manifest` checks unit hashes, recorded Survivors and equivalent markers. It runs no
43
+ test, so it does not check tests digests.
44
+
45
+ ### Migration
46
+
47
+ - Delete the seven removed flags from scripts and CI. The run needs no prior coverage step, and
48
+ `--pytest-args` still passes through to pytest.
49
+ - A Survivor recorded on an unchanged function now fails the run (exit 3) until it is killed or
50
+ accepted with `# mutate4py: equivalent <reason>`.
51
+ - A 0.2.x Manifest entry with Survivors and no tests digest is retried once.
52
+ - To keep the 1.0 s floor, pass `--min-timeout 1.0`.
@@ -48,6 +48,13 @@ _Avoid_: cache, state file, lockfile
48
48
  A Function unit's structural fingerprint. Reformatting and comment edits leave it
49
49
  alone; behaviour-affecting edits change it.
50
50
 
51
+ **Tests digest**:
52
+ A Function unit's SHA-256 over the test inputs that can reach its covered Sites: the
53
+ test files the Measurement shows reaching them, their conftests, the run's support
54
+ files, the pytest table of `pyproject.toml`, the pytest ini file and `uv.lock`. Stored
55
+ beside the Function unit's score. A stored digest that no longer matches the fresh one
56
+ re-scores that unit (ADR 0029).
57
+
51
58
  **Differential rerun**:
52
59
  Re-testing only the Sites whose Function unit changed since the last Manifest. The
53
60
  default once a Manifest exists.
@@ -58,30 +65,65 @@ under the Function unit's `accepted` list with its reason; it does not fail the
58
65
  _Avoid_: ignored, suppressed
59
66
 
60
67
  **Unscored**:
61
- A Function unit whose Manifest entry has no `score` (written before 0.1.5, or never
62
- fully selected by a scored run). `--check-manifest` judges it on hashes alone.
68
+ A Function unit with no stored entry, or whose Manifest entry has no `score` (written before
69
+ 0.1.5, or never scored by a run). A default run scores it (ADR 0029 rule 1). `--check-manifest` judges it on hashes alone.
63
70
  _Avoid_: clean
64
71
 
65
72
  ### Coverage
66
73
 
67
74
  **Coverage gate**:
68
- The line-coverage filter that splits discovered Sites into covered and uncovered.
69
- Branch data is deliberately ignored.
75
+ The line-coverage filter that splits discovered Sites into covered and uncovered. It
76
+ reads the Measurement's data; a line is covered when any context reached it, Empty
77
+ context included. Branch data is deliberately ignored.
70
78
  _Avoid_: coverage filter, eligibility check
71
79
 
72
80
  **covered / uncovered**:
73
81
  The two disjoint, exhaustive partitions of the discovered Sites.
74
82
 
83
+ **Measurement**:
84
+ The one pytest session a scored run takes before any Mutant runs: the run's unit tests
85
+ in the Project environment, under coverage.py's `ctrace` core, each test in its own
86
+ context. Its data gives the Baseline, the Coverage gate and the Test map (ADR 0027).
87
+ _Avoid_: coverage run, pre-run
88
+
75
89
  **Baseline**:
76
- One run of the test suite against unmutated source. It must pass, and its duration
77
- sets the Mutant timeout.
90
+ The Measurement's pass requirement: the unmutated suite must pass in the Measurement,
91
+ or no Mutant runs. The Measurement's wall time gives the Per-Mutant overhead (wall
92
+ time minus summed test durations), which is part of every Mutant's timeout.
93
+
94
+ **Test map**:
95
+ The named tests that reached each covered line in the Measurement. Dispatch runs a
96
+ Site's narrowed tests from it.
97
+ _Avoid_: coverage contexts, test list
78
98
 
79
99
  ### Execution
80
100
 
81
101
  **Executor**:
82
102
  The interface both Mutant-execution backends implement — prime once, then run a test
83
- argument list under a timeout and classify. The forking executor is the fast path;
84
- the subprocess executor is the always-correct fallback.
103
+ argument list under a timeout and classify. The forking executor runs Mutants from a
104
+ Fork server, each by fresh import, Swap mode or a fresh interpreter. The subprocess
105
+ executor is the whole-run fallback where forking is impossible or unsafe, and it runs
106
+ every Survivor confirmation.
107
+
108
+ **Fork server**:
109
+ The `_worker_server` process a serial forking run starts over the project tree itself,
110
+ under the Project environment. It primes the forking executor once and forks one child
111
+ per Mutant, so the orchestrator's state never reaches a Mutant.
112
+
113
+ **Harness module**:
114
+ A file loaded before the Fork server primes: part of the server's own code, the executor
115
+ and protocol code a child runs around pytest. Each Mutant in it runs in a fresh interpreter,
116
+ because a swap would change the code that reports the child's result.
117
+
118
+ **Swap mode**:
119
+ The forking executor's way of running a file the prime imported (a conftest or a
120
+ plugin): the forked child swaps the mutated function bodies into the live functions and
121
+ runs pytest in-process. A Mutant the swap refuses runs in a fresh interpreter instead.
122
+
123
+ **Survivor confirmation**:
124
+ The re-run of every Survivor through the subprocess executor with the full test set,
125
+ before a Survivor is reported. A narrowed selection or a swap can let a Mutant survive
126
+ that the full test set kills, so a Survivor is not trusted until this run agrees.
85
127
 
86
128
  **Worker**:
87
129
  An isolated tree copy of the working directory with its own provisioned environment,
@@ -98,7 +140,7 @@ A symlink-only directory that supplies the `mutate4py` package to a Worker venv
98
140
  The target project's `.venv`, or a `uv run --with` overlay on it; scored runs must run under it.
99
141
 
100
142
  **Selected sites**:
101
- The covered Sites actually mutated, after line filtering and differential selection.
143
+ The covered Sites actually mutated, after differential selection.
102
144
 
103
145
  **Classification**:
104
146
  The per-Site verdict: **killed**, **survived**, or **timeout**. Timeout is visible
@@ -108,46 +150,10 @@ _Avoid_: result, status, outcome (reserve *outcome* for Selection outcome)
108
150
  ### Test selection
109
151
 
110
152
  **Selection outcome**:
111
- What the test-context db says about a Site's line — **narrowed** (named tests cover
112
- it, so only those run), **static** (import-time code no single test owns, so the
113
- full test set runs), **under-listed** (see below; also runs the full test set), or
114
- a disagreement.
153
+ What the Measurement says about a Site's line — **narrowed** (named tests reach it, so
154
+ only those run) or **static** (import-time code no single test owns, or a line no test
155
+ reached, so the full test set runs).
115
156
 
116
157
  **Empty context**:
117
158
  Coverage.py's whole-run context, as opposed to a named per-test one. A line matched
118
- only by it ran at import time. A signal, not noise.
119
-
120
- **Under-listed**:
121
- A Selection outcome (issue #69): the test-context db names covering tests for a
122
- Site's line, but at least one was recorded by a *dynamic* (`pytest
123
- --cov-context=test`, i.e. `switch_context()`) context rather than a static
124
- (isolated-session) one. ADR 0021 proved a dynamic-context collector session
125
- silently drops every covering test but the first to reach a shared line or arc, so
126
- such a list can never be trusted complete — it is never returned as `narrowed`.
127
- mutate4py runs the full test set instead, same dispatch as `static` but tallied
128
- apart from it (`degraded` in the run summary) so the report can't claim `static 0`
129
- while Sites were actually rejected. Detected by the `|setup`/`|run`/`|teardown`
130
- suffix pytest-cov appends to a dynamic context's name — an isolated-session build
131
- (`--build-test-contexts`) never produces that suffix.
132
- _Avoid_: stale context, incomplete narrowing
133
-
134
- **Selection disagreement**:
135
- The test-context db and the Coverage gate contradicting each other. Always an input
136
- defect — a stale db or a path mismatch — never uncovered code, so it aborts the run
137
- rather than falling back. Distinct from **under-listed**: a disagreement means the db
138
- can't account for the line at all (no safe fallback exists); under-listed means it
139
- names some tests but can't prove the list complete (falling back to the full test set
140
- is safe).
141
-
142
- **Test-context cache**:
143
- The sidecar fingerprint of the inputs a test-context db was built from, letting a
144
- later `--build-test-contexts` run skip the rebuild when none of them changed. Named
145
- *cache* — not *manifest* — deliberately: the Manifest is a durable record of what was
146
- mutation-tested and belongs in the repo, whereas this is disposable, gitignored, and
147
- rebuilt from scratch whenever it can't prove itself fresh.
148
- _Avoid_: test-context manifest, context manifest
149
-
150
- **Freshness / staleness**:
151
- Whether the Test-context cache can prove nothing that would change the db has
152
- changed. Deliberately one-way: fresh means proven unchanged, stale means anything
153
- else, including "couldn't tell".
159
+ only by it ran at import time.