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.
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/roles/QA.md +0 -6
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/roles/hardender.md +8 -9
- mutate4py-0.3.0/.github/workflows/ci.yml +61 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/AGENTS.md +6 -4
- mutate4py-0.3.0/CHANGELOG.md +52 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/CONTEXT.md +55 -49
- mutate4py-0.3.0/PKG-INFO +404 -0
- mutate4py-0.3.0/README.md +388 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/run_acceptance.sh +8 -74
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/steps/cli_surface_helpers.py +54 -73
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/steps/cli_surface_steps.py +48 -51
- mutate4py-0.3.0/acceptance/steps/coverage_helpers.py +104 -0
- mutate4py-0.3.0/acceptance/steps/coverage_steps.py +190 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/steps/manifest_steps.py +50 -0
- mutate4py-0.3.0/acceptance/steps/parallel_workers_helpers.py +102 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/steps/parallel_workers_steps.py +141 -204
- mutate4py-0.3.0/acceptance/steps/run_loop_helpers.py +81 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/steps/run_loop_steps.py +65 -181
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/steps/site_discovery_steps.py +79 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/steps/step_lib.py +0 -32
- mutate4py-0.3.0/acceptance/steps/test_selection_steps.py +392 -0
- mutate4py-0.3.0/acceptance/steps/tests_digest_helpers.py +124 -0
- mutate4py-0.3.0/acceptance/steps/tests_digest_steps.py +206 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0007-coverage-default-path-and-line-only-gate.md +9 -1
- {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0008-coverage-flags-pairwise-exclusive.md +10 -1
- mutate4py-0.3.0/docs/adr/0011-timeout-printed-verbatim-folded-into-killed.md +46 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0018-test-selection-three-case-no-silent-fallback.md +56 -1
- {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0020-single-execution-model-and-plugin-neutralisation.md +19 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0021-test-context-db-isolated-session-build.md +1 -1
- {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0022-test-context-cache-sweeps-the-whole-project-tree.md +1 -1
- {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0023-unit-component-integration-test-split.md +23 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0024-unary-not-and-minus-sites.md +5 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0026-survivors-fail-the-run-and-stay-in-the-manifest.md +31 -3
- mutate4py-0.3.0/docs/adr/0027-single-session-ctrace-measurement-unit-only-scoring.md +71 -0
- mutate4py-0.3.0/docs/adr/0028-fork-server-harness-mode-and-swap-mode.md +70 -0
- mutate4py-0.3.0/docs/adr/0029-per-function-tests-digest-and-skip-rule.md +160 -0
- mutate4py-0.3.0/docs/demo/111-demo1-fresh-worktree-loop.txt +153 -0
- mutate4py-0.3.0/docs/demo/111-demo2-v0.2.0-just-mutate-cold.txt +130 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/features/cli-surface.feature +57 -73
- mutate4py-0.3.0/features/coverage.feature +118 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/features/manifest.feature +14 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/features/parallel-workers.feature +21 -16
- {mutate4py-0.2.0 → mutate4py-0.3.0}/features/run-loop.feature +31 -42
- {mutate4py-0.2.0 → mutate4py-0.3.0}/features/site-discovery.feature +14 -0
- mutate4py-0.3.0/features/test-selection.feature +97 -0
- mutate4py-0.3.0/features/tests-digest.feature +137 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/justfile +172 -120
- {mutate4py-0.2.0 → mutate4py-0.3.0}/pyproject.toml +7 -7
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/__main__.py +19 -52
- mutate4py-0.3.0/src/mutate4py/__main__.py.manifest.json +54 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_cli_validation.py +23 -41
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_cli_validation.py.manifest.json +43 -64
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_cmd.py.manifest.json +19 -3
- mutate4py-0.3.0/src/mutate4py/_code_swap.py +254 -0
- mutate4py-0.3.0/src/mutate4py/_code_swap.py.manifest.json +309 -0
- mutate4py-0.3.0/src/mutate4py/_digest_changes.py +24 -0
- mutate4py-0.3.0/src/mutate4py/_digest_changes.py.manifest.json +39 -0
- mutate4py-0.3.0/src/mutate4py/_digest_inputs.py +58 -0
- mutate4py-0.3.0/src/mutate4py/_digest_inputs.py.manifest.json +43 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_dispatch.py +88 -137
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_dispatch.py.manifest.json +50 -57
- mutate4py-0.3.0/src/mutate4py/_execution.py +238 -0
- mutate4py-0.3.0/src/mutate4py/_execution.py.manifest.json +36 -0
- mutate4py-0.3.0/src/mutate4py/_executor.py +140 -0
- mutate4py-0.3.0/src/mutate4py/_executor.py.manifest.json +106 -0
- mutate4py-0.3.0/src/mutate4py/_executor_selection.py +118 -0
- mutate4py-0.3.0/src/mutate4py/_executor_selection.py.manifest.json +36 -0
- mutate4py-0.3.0/src/mutate4py/_exit_codes.py.manifest.json +54 -0
- mutate4py-0.3.0/src/mutate4py/_fork_child.py +117 -0
- mutate4py-0.3.0/src/mutate4py/_fork_child.py.manifest.json +106 -0
- mutate4py-0.3.0/src/mutate4py/_forking_executor.py +351 -0
- mutate4py-0.3.0/src/mutate4py/_forking_executor.py.manifest.json +338 -0
- mutate4py-0.3.0/src/mutate4py/_fresh_digests.py +38 -0
- mutate4py-0.3.0/src/mutate4py/_fresh_digests.py.manifest.json +29 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_manifest.py +71 -33
- mutate4py-0.3.0/src/mutate4py/_manifest.py.manifest.json +384 -0
- mutate4py-0.3.0/src/mutate4py/_measure_plugin.py +80 -0
- mutate4py-0.3.0/src/mutate4py/_measure_plugin.py.manifest.json +71 -0
- mutate4py-0.3.0/src/mutate4py/_measure_record.py +196 -0
- mutate4py-0.3.0/src/mutate4py/_measure_record.py.manifest.json +92 -0
- mutate4py-0.3.0/src/mutate4py/_measurement.py +362 -0
- mutate4py-0.3.0/src/mutate4py/_measurement.py.manifest.json +204 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_plugin_neutralisation.py +16 -1
- mutate4py-0.3.0/src/mutate4py/_plugin_neutralisation.py.manifest.json +22 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_py_tree.py +4 -4
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_py_tree.py.manifest.json +3 -3
- mutate4py-0.3.0/src/mutate4py/_pytest_argv.py +79 -0
- mutate4py-0.3.0/src/mutate4py/_pytest_argv.py.manifest.json +29 -0
- mutate4py-0.3.0/src/mutate4py/_pytest_config.py +79 -0
- mutate4py-0.3.0/src/mutate4py/_pytest_config.py.manifest.json +43 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_report.py +80 -78
- mutate4py-0.3.0/src/mutate4py/_report.py.manifest.json +279 -0
- mutate4py-0.3.0/src/mutate4py/_run_request.py +33 -0
- mutate4py-0.3.0/src/mutate4py/_run_request.py.manifest.json +7 -0
- mutate4py-0.3.0/src/mutate4py/_runner.py +395 -0
- mutate4py-0.3.0/src/mutate4py/_runner.py.manifest.json +189 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_scoring.py +13 -7
- mutate4py-0.3.0/src/mutate4py/_scoring.py.manifest.json +159 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_sidecar_io.py +5 -7
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_sidecar_io.py.manifest.json +11 -11
- mutate4py-0.3.0/src/mutate4py/_site_selection.py +46 -0
- mutate4py-0.3.0/src/mutate4py/_site_selection.py.manifest.json +69 -0
- mutate4py-0.3.0/src/mutate4py/_skip_facts.py +83 -0
- mutate4py-0.3.0/src/mutate4py/_skip_facts.py.manifest.json +29 -0
- mutate4py-0.3.0/src/mutate4py/_skipped_run.py +50 -0
- mutate4py-0.3.0/src/mutate4py/_skipped_run.py.manifest.json +22 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_source_loading.py +68 -27
- mutate4py-0.3.0/src/mutate4py/_source_loading.py.manifest.json +114 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_subprocess_executor.py +6 -1
- mutate4py-0.3.0/src/mutate4py/_subprocess_executor.py.manifest.json +36 -0
- mutate4py-0.3.0/src/mutate4py/_test_dispatch.py +160 -0
- mutate4py-0.3.0/src/mutate4py/_test_dispatch.py.manifest.json +57 -0
- mutate4py-0.3.0/src/mutate4py/_tests_digest.py +69 -0
- mutate4py-0.3.0/src/mutate4py/_tests_digest.py.manifest.json +29 -0
- mutate4py-0.3.0/src/mutate4py/_tests_fingerprint.py +267 -0
- mutate4py-0.3.0/src/mutate4py/_tests_fingerprint.py.manifest.json +169 -0
- mutate4py-0.3.0/src/mutate4py/_unscored.py +20 -0
- mutate4py-0.3.0/src/mutate4py/_unscored.py.manifest.json +24 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_worker_protocol.py +84 -20
- mutate4py-0.3.0/src/mutate4py/_worker_protocol.py.manifest.json +134 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_worker_provision.py +11 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_worker_provision.py.manifest.json +22 -15
- mutate4py-0.3.0/src/mutate4py/_worker_results.py +64 -0
- mutate4py-0.3.0/src/mutate4py/_worker_results.py.manifest.json +43 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_worker_server.py +25 -6
- mutate4py-0.3.0/src/mutate4py/_worker_server.py.manifest.json +50 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_workers.py +117 -103
- mutate4py-0.3.0/src/mutate4py/_workers.py.manifest.json +85 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tach.toml +92 -23
- mutate4py-0.3.0/tests/_digest_project.py +62 -0
- mutate4py-0.3.0/tests/_dispatch_helpers.py +28 -0
- mutate4py-0.3.0/tests/_measurement_helpers.py +69 -0
- mutate4py-0.3.0/tests/_worker_fakes_helpers.py +39 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/conftest.py +5 -3
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/polls/models.py +2 -2
- mutate4py-0.3.0/tests/fixtures/django_project/tests/test_polls.py +32 -0
- mutate4py-0.3.0/tests/fixtures/measure_plain/calc.py +15 -0
- mutate4py-0.3.0/tests/fixtures/measure_plain/tests/conftest.py +9 -0
- mutate4py-0.3.0/tests/fixtures/measure_plain/tests/helpers.py +2 -0
- mutate4py-0.3.0/tests/fixtures/measure_plain/tests/test_calc.py +15 -0
- mutate4py-0.3.0/tests/fixtures/measure_plain/tests/test_slow.py +6 -0
- mutate4py-0.3.0/tests/fixtures/plain_project/pytest.ini +3 -0
- mutate4py-0.3.0/tests/test_baseline.py +96 -0
- mutate4py-0.3.0/tests/test_batch_time.py +98 -0
- mutate4py-0.3.0/tests/test_ci_workflow_tools.py +71 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_cli_surface_helpers.py +95 -52
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_cli_validation.py +39 -136
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_cmd.py +7 -4
- mutate4py-0.3.0/tests/test_code_swap.py +262 -0
- mutate4py-0.3.0/tests/test_coverage_helpers.py +160 -0
- mutate4py-0.3.0/tests/test_digest_inputs.py +82 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_discovery.py +58 -3
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_dispatch.py +34 -466
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_dispatch_cli.py +21 -7
- mutate4py-0.3.0/tests/test_dispatch_measurement.py +88 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_django_workers_integration.py +28 -33
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_env_overlay_integration.py +0 -12
- mutate4py-0.3.0/tests/test_execution.py +480 -0
- mutate4py-0.3.0/tests/test_executor_composition.py +487 -0
- mutate4py-0.3.0/tests/test_executor_description.py +68 -0
- mutate4py-0.3.0/tests/test_executor_selection.py +217 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_exit_codes.py +12 -1
- mutate4py-0.3.0/tests/test_fork_child.py +162 -0
- mutate4py-0.3.0/tests/test_forking_executor.py +596 -0
- mutate4py-0.3.0/tests/test_fresh_digests.py +83 -0
- mutate4py-0.3.0/tests/test_install_tools.py +187 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_integration_marker_guard.py +1 -1
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_main.py +38 -130
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_main_cli_flags.py +7 -66
- mutate4py-0.3.0/tests/test_manifest_fingerprint.py +232 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_manifest_modes.py +55 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_manifest_score.py +226 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_manifest_storage.py +27 -0
- mutate4py-0.3.0/tests/test_measure_record.py +147 -0
- mutate4py-0.3.0/tests/test_measurement.py +137 -0
- mutate4py-0.3.0/tests/test_measurement_gate.py +279 -0
- mutate4py-0.3.0/tests/test_measurement_index.py +284 -0
- mutate4py-0.3.0/tests/test_mutate_recipe_report.py +85 -0
- mutate4py-0.3.0/tests/test_mutate_unit_only.py +227 -0
- mutate4py-0.3.0/tests/test_no_change_rerun.py +169 -0
- mutate4py-0.3.0/tests/test_parallel_workers_helpers.py +156 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_plugin_neutralisation.py +15 -0
- mutate4py-0.3.0/tests/test_pytest_argv.py +43 -0
- mutate4py-0.3.0/tests/test_pytest_config.py +106 -0
- mutate4py-0.3.0/tests/test_removed_coverage_flags.py +74 -0
- mutate4py-0.3.0/tests/test_removed_selection_flags.py +78 -0
- mutate4py-0.3.0/tests/test_removed_test_context_flags.py +84 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_report.py +51 -32
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_run_acceptance_script.py +1 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_run_idempotence.py +7 -17
- mutate4py-0.3.0/tests/test_run_loop_helpers.py +151 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_run_mutations_modes.py +54 -263
- mutate4py-0.3.0/tests/test_run_tests_digest.py +407 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_runner.py +17 -137
- mutate4py-0.3.0/tests/test_runner_measurement.py +280 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_scoring.py +50 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_site_selection.py +11 -60
- mutate4py-0.3.0/tests/test_skip_facts.py +175 -0
- mutate4py-0.3.0/tests/test_skipped_run.py +88 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_source_loading.py +37 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_subprocess_executor.py +6 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_survivor_gate.py +149 -82
- mutate4py-0.3.0/tests/test_test_dispatch.py +183 -0
- mutate4py-0.3.0/tests/test_tests_digest.py +181 -0
- mutate4py-0.3.0/tests/test_tests_digest_helpers.py +93 -0
- mutate4py-0.3.0/tests/test_tests_fingerprint.py +404 -0
- mutate4py-0.3.0/tests/test_tool_preflight.py +103 -0
- mutate4py-0.3.0/tests/test_unscored.py +64 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_worker_protocol.py +149 -28
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_worker_server.py +35 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_workers.py +92 -328
- mutate4py-0.3.0/tests/test_workers_dispatch.py +351 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/uv.lock +29 -1
- mutate4py-0.2.0/.github/workflows/ci.yml +0 -96
- mutate4py-0.2.0/PKG-INFO +0 -310
- mutate4py-0.2.0/README.md +0 -295
- mutate4py-0.2.0/acceptance/steps/cli_surface_qa_steps.py +0 -306
- mutate4py-0.2.0/acceptance/steps/coverage_helpers.py +0 -187
- mutate4py-0.2.0/acceptance/steps/coverage_qa_steps.py +0 -266
- mutate4py-0.2.0/acceptance/steps/coverage_steps.py +0 -206
- mutate4py-0.2.0/acceptance/steps/manifest_qa_steps.py +0 -247
- mutate4py-0.2.0/acceptance/steps/parallel_workers_qa_helpers.py +0 -105
- mutate4py-0.2.0/acceptance/steps/parallel_workers_qa_steps.py +0 -561
- mutate4py-0.2.0/acceptance/steps/run_loop_helpers.py +0 -86
- mutate4py-0.2.0/acceptance/steps/run_loop_qa_helpers.py +0 -94
- mutate4py-0.2.0/acceptance/steps/run_loop_qa_steps.py +0 -351
- mutate4py-0.2.0/acceptance/steps/site_discovery_qa_steps.py +0 -198
- mutate4py-0.2.0/acceptance/steps/test_selection_steps.py +0 -505
- mutate4py-0.2.0/docs/adr/0011-timeout-printed-verbatim-folded-into-killed.md +0 -28
- mutate4py-0.2.0/features/cli-surface_qa.feature +0 -126
- mutate4py-0.2.0/features/coverage.feature +0 -170
- mutate4py-0.2.0/features/coverage_qa.feature +0 -143
- mutate4py-0.2.0/features/manifest_qa.feature +0 -100
- mutate4py-0.2.0/features/parallel-workers_qa.feature +0 -152
- mutate4py-0.2.0/features/run-loop_qa.feature +0 -142
- mutate4py-0.2.0/features/site-discovery_qa.feature +0 -97
- mutate4py-0.2.0/features/test-selection.feature +0 -121
- mutate4py-0.2.0/scripts/check_context_deselection.py +0 -194
- mutate4py-0.2.0/src/mutate4py/__main__.py.manifest.json +0 -29
- mutate4py-0.2.0/src/mutate4py/_baseline.py +0 -115
- mutate4py-0.2.0/src/mutate4py/_baseline.py.manifest.json +0 -57
- mutate4py-0.2.0/src/mutate4py/_coverage.py +0 -116
- mutate4py-0.2.0/src/mutate4py/_coverage.py.manifest.json +0 -57
- mutate4py-0.2.0/src/mutate4py/_execution.py +0 -152
- mutate4py-0.2.0/src/mutate4py/_execution.py.manifest.json +0 -29
- mutate4py-0.2.0/src/mutate4py/_executor.py +0 -19
- mutate4py-0.2.0/src/mutate4py/_executor.py.manifest.json +0 -22
- mutate4py-0.2.0/src/mutate4py/_executor_selection.py +0 -66
- mutate4py-0.2.0/src/mutate4py/_executor_selection.py.manifest.json +0 -22
- mutate4py-0.2.0/src/mutate4py/_exit_codes.py.manifest.json +0 -29
- mutate4py-0.2.0/src/mutate4py/_forking_executor.py +0 -298
- mutate4py-0.2.0/src/mutate4py/_forking_executor.py.manifest.json +0 -99
- mutate4py-0.2.0/src/mutate4py/_manifest.py.manifest.json +0 -183
- mutate4py-0.2.0/src/mutate4py/_plugin_neutralisation.py.manifest.json +0 -15
- mutate4py-0.2.0/src/mutate4py/_report.py.manifest.json +0 -106
- mutate4py-0.2.0/src/mutate4py/_runner.py +0 -382
- mutate4py-0.2.0/src/mutate4py/_runner.py.manifest.json +0 -71
- mutate4py-0.2.0/src/mutate4py/_scoring.py.manifest.json +0 -78
- mutate4py-0.2.0/src/mutate4py/_site_selection.py +0 -92
- mutate4py-0.2.0/src/mutate4py/_site_selection.py.manifest.json +0 -57
- mutate4py-0.2.0/src/mutate4py/_source_loading.py.manifest.json +0 -43
- mutate4py-0.2.0/src/mutate4py/_subprocess_executor.py.manifest.json +0 -29
- mutate4py-0.2.0/src/mutate4py/_test_collection.py +0 -120
- mutate4py-0.2.0/src/mutate4py/_test_collection.py.manifest.json +0 -57
- mutate4py-0.2.0/src/mutate4py/_test_context_build.py +0 -133
- mutate4py-0.2.0/src/mutate4py/_test_context_build.py.manifest.json +0 -43
- mutate4py-0.2.0/src/mutate4py/_test_context_cache.py +0 -229
- mutate4py-0.2.0/src/mutate4py/_test_context_cache.py.manifest.json +0 -127
- mutate4py-0.2.0/src/mutate4py/_test_context_orchestration.py +0 -75
- mutate4py-0.2.0/src/mutate4py/_test_context_orchestration.py.manifest.json +0 -15
- mutate4py-0.2.0/src/mutate4py/_test_dispatch.py +0 -99
- mutate4py-0.2.0/src/mutate4py/_test_dispatch.py.manifest.json +0 -29
- mutate4py-0.2.0/src/mutate4py/_test_selection.py +0 -182
- mutate4py-0.2.0/src/mutate4py/_test_selection.py.manifest.json +0 -78
- mutate4py-0.2.0/src/mutate4py/_worker_protocol.py.manifest.json +0 -99
- mutate4py-0.2.0/src/mutate4py/_worker_server.py.manifest.json +0 -43
- mutate4py-0.2.0/src/mutate4py/_workers.py.manifest.json +0 -99
- mutate4py-0.2.0/tests/_coverage_db_helpers.py +0 -28
- mutate4py-0.2.0/tests/_fork_unsafe_plugin_helpers.py +0 -23
- mutate4py-0.2.0/tests/fixtures/django_project/tests/test_polls.py +0 -20
- mutate4py-0.2.0/tests/fixtures/overlapping_coverage/.coveragerc +0 -2
- mutate4py-0.2.0/tests/fixtures/overlapping_coverage/shared.py +0 -10
- mutate4py-0.2.0/tests/fixtures/overlapping_coverage/test_a.py +0 -6
- mutate4py-0.2.0/tests/fixtures/overlapping_coverage/test_b.py +0 -6
- mutate4py-0.2.0/tests/test_baseline.py +0 -256
- mutate4py-0.2.0/tests/test_check_context_deselection.py +0 -277
- mutate4py-0.2.0/tests/test_coverage.py +0 -394
- mutate4py-0.2.0/tests/test_coverage_helpers.py +0 -330
- mutate4py-0.2.0/tests/test_execution.py +0 -275
- mutate4py-0.2.0/tests/test_executor_composition.py +0 -357
- mutate4py-0.2.0/tests/test_executor_selection.py +0 -163
- mutate4py-0.2.0/tests/test_forking_executor.py +0 -587
- mutate4py-0.2.0/tests/test_parallel_workers_qa_helpers.py +0 -143
- mutate4py-0.2.0/tests/test_run_loop_qa_helpers.py +0 -134
- mutate4py-0.2.0/tests/test_test_collection.py +0 -139
- mutate4py-0.2.0/tests/test_test_context_build.py +0 -301
- mutate4py-0.2.0/tests/test_test_context_cache.py +0 -510
- mutate4py-0.2.0/tests/test_test_context_orchestration.py +0 -261
- mutate4py-0.2.0/tests/test_test_selection.py +0 -639
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/backlog.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/ledger-project.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/ledger-swarmforge.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/references/equivalent-mutants.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/roles/cleaner.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/roles/coder.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/agent-retro/SKILL.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/agent-retro/scripts/extract.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/domain-modeling/ADR-FORMAT.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/domain-modeling/CONTEXT-FORMAT.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/domain-modeling/SKILL.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/explain/SKILL.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/grill-with-docs/SKILL.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/grilling/SKILL.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/recall/SKILL.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/replay/SKILL.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/review/SKILL.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/review/references/review-rules.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/search/SKILL.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/session-crosslink/SKILL.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/session-handoff/SKILL.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/session-to-skill/SKILL.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/setup-swarm/SKILL.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/teach/SKILL.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/using-entire/SKILL.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.agents/skills/what-happened/SKILL.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.claude/agents/entire-search.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.claude/settings.json +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.entire/.gitignore +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.entire/settings.json +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.github/workflows/release.yml +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.gitignore +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/.python-version +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/CLAUDE.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/LICENSE +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/__init__.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/fixtures/empty_units.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/fixtures/mixed_operators.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/fixtures/module_level.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/fixtures/plain.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/fixtures/stale.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/fixtures/unary_operators.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/generate_acceptance.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/run_gherkin_mutation.sh +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/runner_adapter.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/acceptance/steps/__init__.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0001-site-discovery-and-operators.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0005-manifest-hash-is-ast-unparse.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0006-update-manifest-idempotent.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0015-parallel-workers-via-uv-clone-per-worker.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0017-multi-root-targets-and-uv-workspace-autodiscovery.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/docs/adr/0025-scored-runs-refuse-a-foreign-interpreter.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/features/ci.feature +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/__init__.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/__init__.py.manifest.json +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_bytecode_cache.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_bytecode_cache.py.manifest.json +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_cmd.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_discovery.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_discovery.py.manifest.json +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_django_worker_plugin.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_django_worker_plugin.py.manifest.json +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_equivalent.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_equivalent.py.manifest.json +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_exit_codes.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_glob_dialect.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_glob_dialect.py.manifest.json +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_ids.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_ids.py.manifest.json +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_manifest_modes.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_manifest_modes.py.manifest.json +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_manifest_storage.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_manifest_storage.py.manifest.json +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_target_resolution.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_target_resolution.py.manifest.json +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_workspace.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/src/mutate4py/_workspace.py.manifest.json +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarm +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/constitution/articles/engineering.prompt +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/constitution/articles/handoffs.prompt +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/constitution/articles/local-engineering.prompt +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/constitution/articles/local-workflow.prompt +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/constitution/articles/project.prompt +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/constitution/articles/workflow.prompt +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/constitution.prompt +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/roles/QA.prompt +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/roles/architect.prompt +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/roles/cleaner.prompt +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/roles/coder.prompt +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/roles/curator.prompt +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/roles/hardender.prompt +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/roles/integrator.prompt +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/roles/specifier.prompt +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/roles/ux-engineer.prompt +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/skills/agent-retro/SKILL.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/skills/agent-retro/scripts/extract.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/skills/setup-swarm/SKILL.md +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/swarmforge.conf +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/swarmforge/templates/feature.feature +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/__init__.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/_module_size_guard.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/_pytest_project_helpers.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/calc.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/polls/__init__.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/polls/apps.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/polls/migrations/0001_initial.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/polls/migrations/__init__.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/pyproject.toml +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/pytest.ini +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/settings.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/tests/__init__.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/django_project/tests/test_calc.py +0 -0
- {mutate4py-0.2.0/tests/fixtures/plain_project → mutate4py-0.3.0/tests/fixtures/measure_plain}/pytest.ini +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/plain_project/calc.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/plain_project/pyproject.toml +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/fixtures/plain_project/tests/test_calc.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_django_worker_plugin.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_equivalent.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_glob_dialect.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_hash_stability.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_manifest.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_module_size_guard.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_properties.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_py_tree.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_sidecar_io.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_target_resolution.py +0 -0
- {mutate4py-0.2.0 → mutate4py-0.3.0}/tests/test_worker_provision.py +0 -0
- {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
|
|
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
|
|
14
|
-
`_runner.py` prints one `[i/total] <status> line N ...` line per mutation site unconditionally on a scored run
|
|
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
|
|
16
|
+
Invoke scored runs through the justfile instead of calling `mutate4py` directly:
|
|
17
17
|
```
|
|
18
|
-
just
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
-
- `
|
|
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
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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
|
|
84
|
-
|
|
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
|
|
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
|
|
112
|
-
|
|
113
|
-
|
|
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.
|
|
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.
|