pytest-selective 1.1.2__tar.gz → 1.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 (198) hide show
  1. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/ci.yml +4 -5
  2. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/pr-title.yml +2 -1
  3. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/public-actions.yml +0 -2
  4. pytest_selective-1.3.0/.github/workflows/weekly-checkpoint.yml +21 -0
  5. pytest_selective-1.3.0/.release-please-manifest.json +3 -0
  6. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/CHANGELOG.md +21 -0
  7. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/CONTRIBUTING.md +13 -6
  8. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/PKG-INFO +2 -2
  9. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/README.md +1 -1
  10. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/action.yml +6 -0
  11. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/actions/plan-lanes/action.yml +8 -0
  12. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/actions/test-impact/action.yml +8 -0
  13. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/contracts/selection/conftest.py +2 -1
  14. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/contracts/selection/runtime_deps.py +4 -0
  15. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/ci.md +8 -0
  16. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/configuration.md +52 -1
  17. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/selection-health.md +2 -1
  18. pytest_selective-1.3.0/docs/shared-plan-execution.md +43 -0
  19. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/pyproject.toml +1 -1
  20. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/ci_file_config.py +1 -8
  21. pytest_selective-1.3.0/scripts/run_ci_tests.py +8 -0
  22. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/skills/pytest-selective/SKILL.md +71 -18
  23. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/ci.py +32 -1
  24. pytest_selective-1.1.2/scripts/run_ci_tests.py → pytest_selective-1.3.0/src/pytest_selective/execution.py +18 -3
  25. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/git_operations.py +23 -0
  26. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_audit.py +9 -4
  27. pytest_selective-1.3.0/src/pytest_selective/impact_audit_declarations.py +159 -0
  28. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_cli.py +86 -6
  29. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_graph.py +170 -24
  30. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/local.py +3 -5
  31. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_ci_admission.py +1 -0
  32. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_ci_test_execution.py +5 -5
  33. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_repository_ci.py +40 -4
  34. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_impact_audit.py +40 -0
  35. pytest_selective-1.3.0/tests/testing/test_impact_audit_declarations.py +124 -0
  36. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_impact_graph.py +152 -0
  37. pytest_selective-1.3.0/tests/testing/test_pull_request_merge_base.py +83 -0
  38. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_working_tree.py +10 -0
  39. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/uv.lock +1 -1
  40. pytest_selective-1.1.2/.release-please-manifest.json +0 -3
  41. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.dry-lint/baseline.json +0 -0
  42. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.dry-lint/progress.json +0 -0
  43. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.dry-lint.toml +0 -0
  44. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.gitattributes +0 -0
  45. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  46. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  47. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/dependabot.yml +0 -0
  48. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/test-lanes.json +0 -0
  49. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/validation-inputs.json +0 -0
  50. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/docs.yml +0 -0
  51. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/dry-lint-audit.yml +0 -0
  52. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/dry-lint-judge.yml +0 -0
  53. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/dry-lint.yml +0 -0
  54. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/observe-ci.yml +0 -0
  55. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/release-please.yml +0 -0
  56. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/release.yml +0 -0
  57. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.github/workflows/windows-performance.yml +0 -0
  58. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.gitignore +0 -0
  59. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.pre-commit-config.yaml +0 -0
  60. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/.readthedocs.yaml +0 -0
  61. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/AGENTS.md +0 -0
  62. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/CLAUDE.md +0 -0
  63. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/LICENSE +0 -0
  64. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/PROVENANCE.md +0 -0
  65. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/RELEASING.md +0 -0
  66. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/actions/README.md +0 -0
  67. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/actions/pytest-expansion/action.yml +0 -0
  68. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/actions/validation-gate/action.yml +0 -0
  69. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/agentsync.toml +0 -0
  70. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/contracts/selection/README.md +0 -0
  71. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/contracts/selection/quint-connect.json +0 -0
  72. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/contracts/selection/quint.ini +0 -0
  73. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/contracts/selection/selection.qnt +0 -0
  74. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/contracts/selection/selection_driver.py +0 -0
  75. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/contracts/selection/test_selection_contract.py +0 -0
  76. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/README.md +0 -0
  77. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/actions.md +0 -0
  78. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/agents.md +0 -0
  79. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/assets/banner.png +0 -0
  80. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/assets/banner.svg +0 -0
  81. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/assets/brand.json +0 -0
  82. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/assets/icon.png +0 -0
  83. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/assets/logo.svg +0 -0
  84. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/assets/raster-sources.json +0 -0
  85. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/assets/social-preview.png +0 -0
  86. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/assets/social-preview.svg +0 -0
  87. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22-due-ci.json +0 -0
  88. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22-due-docs-ci.json +0 -0
  89. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22-due-docs-widget.json +0 -0
  90. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22-due-widget.json +0 -0
  91. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22-input-replays.json +0 -0
  92. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22-obligation-docs.json +0 -0
  93. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22-obligation.json +0 -0
  94. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22-routing.md +0 -0
  95. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22-selective-docs.json +0 -0
  96. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-22.md +0 -0
  97. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-23-windows-saleor/obligation-baseline.json +0 -0
  98. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after-3.11.xml +0 -0
  99. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after-3.14.xml +0 -0
  100. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after.json +0 -0
  101. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control-3.11.xml +0 -0
  102. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control-3.14.xml +0 -0
  103. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control.json +0 -0
  104. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-earlier.json +0 -0
  105. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/2026-10-23-windows-saleor.md +0 -0
  106. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/benchmarks/windows-runtime.md +0 -0
  107. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/ci-toolkit.md +0 -0
  108. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/comparison-pytest-impacted.md +0 -0
  109. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/evidence.md +0 -0
  110. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/local-development.md +0 -0
  111. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/pytest-hygiene.md +0 -0
  112. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/tutorial.md +0 -0
  113. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/tutorials/adoption-checklist.md +0 -0
  114. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/tutorials/sidecar-dependencies.md +0 -0
  115. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/tutorials/skip-unrelated-ci.md +0 -0
  116. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/work-in-progress/tickets/actions-directory-test-plan.md +0 -0
  117. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/work-in-progress/tickets/codex-selector-correctness-performance-test-plan.md +0 -0
  118. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/work-in-progress/tickets/codex-windows-git-performance-test-plan.md +0 -0
  119. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/work-in-progress/tickets/graph-based-inputs-plan.md +0 -0
  120. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/work-in-progress/tickets/impacted-improvements-test-plan.md +0 -0
  121. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/work-in-progress/tickets/move-action-implementations-test-plan.md +0 -0
  122. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/work-in-progress/tickets/public-action-onboarding-test-plan.md +0 -0
  123. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/work-in-progress/tickets/ratchet-duplicates-zero-test-plan.md +0 -0
  124. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/docs/work-in-progress/tickets/rerun-selection-test-plan.md +0 -0
  125. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/assets/routing-comparison.svg +0 -0
  126. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/check_site.py +0 -0
  127. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/ecosystem.md +0 -0
  128. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/hooks.py +0 -0
  129. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/index.md +0 -0
  130. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/llms.txt +0 -0
  131. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/overrides/main.html +0 -0
  132. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/publishing.md +0 -0
  133. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/requirements.in +0 -0
  134. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/documentation/requirements.txt +0 -0
  135. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/mkdocs.yml +0 -0
  136. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/release-please-config.json +0 -0
  137. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/benchmark_graph.py +0 -0
  138. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/benchmark_parsing.py +0 -0
  139. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/benchmark_rust.py +0 -0
  140. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/check_docs.py +0 -0
  141. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/check_metadata.py +0 -0
  142. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/compare_impacted.py +0 -0
  143. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/measure_ci_runs.py +0 -0
  144. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/public_actions.py +0 -0
  145. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/render_brand.py +0 -0
  146. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/smoke_distribution.py +0 -0
  147. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/scripts/tutorial.py +0 -0
  148. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/skills/pytest-selective/agents/openai.yaml +0 -0
  149. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/__init__.py +0 -0
  150. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/__main__.py +0 -0
  151. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/ast_imports.py +0 -0
  152. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/checkpoint.py +0 -0
  153. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/gate.py +0 -0
  154. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/hygiene.py +0 -0
  155. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_audit_plugin.py +0 -0
  156. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_declarations.py +0 -0
  157. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_explanation.py +0 -0
  158. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_first_party.py +0 -0
  159. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_models.py +0 -0
  160. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_name_scope.py +0 -0
  161. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/impact_selection.py +0 -0
  162. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/input_policy.py +0 -0
  163. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/parse_batch.py +0 -0
  164. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/plugin.py +0 -0
  165. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/py.typed +0 -0
  166. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/pytest_expansion.py +0 -0
  167. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/pytest_plugins.py +0 -0
  168. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/pytest_sharding.py +0 -0
  169. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/test_inventory.py +0 -0
  170. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/typecheck_scope.py +0 -0
  171. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/src/pytest_selective/working_tree.py +0 -0
  172. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/__init__.py +0 -0
  173. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_checkpoint_policy.py +0 -0
  174. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_ci_measurements.py +0 -0
  175. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_distribution_metadata.py +0 -0
  176. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_hygiene.py +0 -0
  177. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_public_actions.py +0 -0
  178. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_pytest_expansion.py +0 -0
  179. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_release_workflow.py +0 -0
  180. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_repository_hygiene.py +0 -0
  181. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_repository_hygiene.py.gv-dependency.yaml +0 -0
  182. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/test_tutorial.py +0 -0
  183. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/__init__.py +0 -0
  184. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/git_repo.py +0 -0
  185. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_ast_imports.py +0 -0
  186. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_ci.py +0 -0
  187. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_gate.py +0 -0
  188. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_git_repo.py +0 -0
  189. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_global_inputs.py +0 -0
  190. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_impact_explanation.py +0 -0
  191. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_impact_first_party.py +0 -0
  192. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_impact_name_scope.py +0 -0
  193. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_parse_batch.py +0 -0
  194. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_portability.py +0 -0
  195. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_pytest_plugin.py +0 -0
  196. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_pytest_plugins.py +0 -0
  197. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_pytest_sharding.py +0 -0
  198. {pytest_selective-1.1.2 → pytest_selective-1.3.0}/tests/testing/test_typecheck_scope.py +0 -0
@@ -6,8 +6,6 @@ on:
6
6
  types: [opened, synchronize, reopened]
7
7
  workflow_dispatch:
8
8
  workflow_call:
9
- schedule:
10
- - cron: '13 3 * * 1'
11
9
  permissions:
12
10
  contents: read
13
11
  concurrency:
@@ -149,12 +147,13 @@ jobs:
149
147
  - name: Record coverage runtime
150
148
  run: uv run --locked python -m coverage --version
151
149
  - name: Execute the shared affected-file plan
152
- run: uv run --locked python scripts/run_ci_tests.py -q -n 4 --durations=20 --junitxml=test-timings.xml
150
+ run: uv run --locked pytest-selective execute-plan -- -q -n 4 --dist=worksteal --durations=20 --junitxml=test-timings.xml
153
151
  env:
154
152
  CI_PLANNER_RESULT: ${{ needs.select-tests.result }}
155
153
  CI_TEST_MODE: ${{ needs.select-tests.outputs.mode }}
156
154
  CI_TEST_FILES: ${{ needs.select-tests.outputs.files }}
157
- CI_COVERAGE: 'true'
155
+ # Full coverage on each OS; 3.11 retains the same compatibility tests without duplicate instrumentation.
156
+ CI_COVERAGE: ${{ matrix.python == '3.14' }}
158
157
  # Keep temporary Git repositories beside the checkout on the work disk.
159
158
  TMP: ${{ runner.temp }}
160
159
  TEMP: ${{ runner.temp }}
@@ -186,7 +185,7 @@ jobs:
186
185
  python-version: '3.11'
187
186
  - run: uv pip install 'pytest==8.0.0'
188
187
  - name: Execute the same file plan on minimum pytest
189
- run: uv run --no-sync python scripts/run_ci_tests.py -q -n 4 --durations=20
188
+ run: uv run --no-sync pytest-selective execute-plan -- -q -n 4 --dist=worksteal --durations=20
190
189
  env:
191
190
  CI_PLANNER_RESULT: ${{ needs.select-tests.result }}
192
191
  CI_TEST_MODE: ${{ needs.select-tests.outputs.mode }}
@@ -2,7 +2,8 @@ name: PR title
2
2
 
3
3
  # Squash merges use this checked title as the Conventional Commit header.
4
4
  on:
5
- pull_request_target:
5
+ # Metadata reads need no privileged target event or repository secrets.
6
+ pull_request:
6
7
  types: [opened, edited, synchronize, reopened, ready_for_review]
7
8
 
8
9
  permissions:
@@ -4,8 +4,6 @@ on:
4
4
  push:
5
5
  branches: [main]
6
6
  workflow_dispatch:
7
- schedule:
8
- - cron: '33 3 * * 1'
9
7
  permissions:
10
8
  contents: read
11
9
  jobs:
@@ -0,0 +1,21 @@
1
+ name: Weekly full checkpoint
2
+ 'on':
3
+ schedule:
4
+ - cron: 13 3 * * 1
5
+ workflow_dispatch: null
6
+ permissions:
7
+ contents: read
8
+ actions: write
9
+ concurrency:
10
+ group: weekly-full-checkpoint
11
+ cancel-in-progress: false
12
+ jobs:
13
+ dispatch:
14
+ runs-on: ubuntu-latest
15
+ timeout-minutes: 5
16
+ steps:
17
+ - uses: gigaverse-app/ci-esta/actions/weekly-checkpoint@v1
18
+ with:
19
+ workflows: '["ci.yml", "public-actions.yml"]'
20
+ ref: ''
21
+ workflow-inputs: '{}'
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.3.0"
3
+ }
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.3.0](https://github.com/gigaverse-app/pytest-selective/compare/v1.2.0...v1.3.0) (2026-11-09)
4
+
5
+
6
+ ### Features
7
+
8
+ * **audit:** show how many tests each written declaration selects ([#124](https://github.com/gigaverse-app/pytest-selective/issues/124)) ([b660301](https://github.com/gigaverse-app/pytest-selective/commit/b660301be82aefb04fdc79202dc83067c28d7eb1))
9
+
10
+
11
+ ### Bug fixes
12
+
13
+ * agent setup round 3, from a third adoption trial ([#127](https://github.com/gigaverse-app/pytest-selective/issues/127)) ([a82bea9](https://github.com/gigaverse-app/pytest-selective/commit/a82bea9f607abcb3ed68009571202ad8b7726c7e))
14
+ * **selection:** keep every module a pytest11 entry-point name maps to ([#122](https://github.com/gigaverse-app/pytest-selective/issues/122)) ([acb6649](https://github.com/gigaverse-app/pytest-selective/commit/acb66492c6440621387e0506c1d0071a5a48979c))
15
+ * **selection:** plan a pull request against its merge commit's actual base ([#119](https://github.com/gigaverse-app/pytest-selective/issues/119)) ([276cec8](https://github.com/gigaverse-app/pytest-selective/commit/276cec84f65c889a1820fd5a001a3a4aca8b7e0d))
16
+
17
+ ## [1.2.0](https://github.com/gigaverse-app/pytest-selective/compare/v1.1.2...v1.2.0) (2026-11-09)
18
+
19
+
20
+ ### Features
21
+
22
+ * **ci:** share plan execution and reduce matrix instrumentation ([1fe0f36](https://github.com/gigaverse-app/pytest-selective/commit/1fe0f36463d4354a99ea3f45406825fa728a12c2))
23
+
3
24
  ## [1.1.2](https://github.com/gigaverse-app/pytest-selective/compare/v1.1.1...v1.1.2) (2026-11-09)
4
25
 
5
26
 
@@ -79,7 +79,9 @@ split indexes and linked worktrees without modifying the user's index or HEAD.
79
79
  CI tests Python 3.11 and 3.14 on Linux, Windows, and macOS. A separate job checks
80
80
  pytest 8.0.0 on Python 3.11, the plugin's lower bound. It also checks hooks, builds both distribution
81
81
  formats, validates PyPI metadata, and installs each distribution in an isolated
82
- environment on both Python versions. Evidence artifacts carry coverage and tutorial output on every platform and Python version.
82
+ environment on both Python versions. JUnit retains results and timings on every platform and Python version.
83
+ Full runs collect coverage on Python 3.14 on all three operating systems; Python 3.11 runs the same compatibility
84
+ tests without a duplicate instrumentation pass. Each runner uses four work-stealing workers.
83
85
 
84
86
  Use `uv lock --upgrade-package NAME` for a targeted dependency update and run the
85
87
  affected checks. Dependabot proposes weekly uv and GitHub Actions updates.
@@ -116,8 +118,8 @@ File planning derives its graph from that same lane config and merges its declar
116
118
  global execution inputs with the graph's environment inputs. Declare subprocess
117
119
  and non-import dependencies at their readers. Full plans collect whole-suite
118
120
  coverage; scoped plans produce JUnit timing evidence without presenting partial
119
- coverage as a whole-suite result. The small execution wrapper only consumes the
120
- public action's JSON file list: it does not implement another selector.
121
+ coverage as a whole-suite result. The public [shared-plan executor](docs/shared-plan-execution.md)
122
+ consumes the action's JSON file list without implementing another selector.
121
123
  The outer pytest process disables the selective plugin to avoid a second planner
122
124
  and a hidden dependency of every test on plugin startup. Plugin integration tests
123
125
  still load it normally in subprocesses and declare that dependency explicitly.
@@ -197,14 +199,19 @@ history or failed planning must execute the full affected lanes and fail the
197
199
  required planning gate. Ordinary PRs and pushes use selection; manual/release/scheduled/full overrides
198
200
  remain complete checkpoints.
199
201
 
200
- CI and public-action smoke run full checkpoints every Monday. Ordinary runs keep
202
+ The **Weekly full checkpoint** workflow dispatches CI and public-action smoke
203
+ every Monday using [CI-esta's shared action](https://github.com/gigaverse-app/ci-esta/blob/main/docs/weekly-checkpoints.md).
204
+ Its small wrapper is generated by `ci-esta checkpoint init`; the source guide
205
+ documents schedule, workflow, ref and input overrides. Ordinary runs keep
201
206
  the shared affected-file plan across all six OS/Python combinations and minimum
202
207
  pytest. `tests/test_tutorial.py` owns the end-to-end tutorial execution and follows
203
208
  that plan; there is no second tutorial invocation in each matrix job. JUnit retains
204
209
  its test result and timing, and full plans also upload whole-suite coverage.
205
210
 
206
- To run a complete checkpoint after a CI change, select **CI** in GitHub Actions
207
- and choose **Run workflow**. Retrying an ordinary PR run preserves its selection;
211
+ To run both complete checkpoints after a CI change, select **Weekly full checkpoint**
212
+ in GitHub Actions and choose **Run workflow**, then inspect the dispatched CI and
213
+ public-action results. A green dispatcher means both requests were accepted.
214
+ You can also dispatch either target directly. Retrying an ordinary PR run preserves its selection;
208
215
  use the manual checkpoint when you need full-suite coverage evidence.
209
216
 
210
217
  ## Duplication
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pytest-selective
3
- Version: 1.1.2
3
+ Version: 1.3.0
4
4
  Summary: Explainable Python test impact analysis: select affected pytest files from Git changes
5
5
  Project-URL: Homepage, https://github.com/gigaverse-app/pytest-selective
6
6
  Project-URL: Documentation, https://pytest-selective.readthedocs.io/en/latest/
@@ -237,7 +237,7 @@ skip pytest, because invoking pytest without paths runs the whole suite.
237
237
  | --- | --- |
238
238
  | A transitive dependency | Import chains and package initialization |
239
239
  | A deleted or renamed module | Dependencies in both the base and head trees |
240
- | Fixtures or startup code | Ancestor conftests, plugins named by `-p` in `addopts`, and configured global modules |
240
+ | Fixtures or startup code | Ancestor conftests, plugins named by `-p` in `addopts`, the project's own `pytest11` entry points, and configured global modules |
241
241
  | Templates, schemas, or data files | [Reader-owned declarations and YAML sidecars](docs/tutorials/sidecar-dependencies.md) |
242
242
  | A surprisingly large selection | Dependency paths, startup-floor and blast-radius explanations |
243
243
  | A large CI suite | Cached parsing, graph reuse, and duration-based shard planning |
@@ -206,7 +206,7 @@ skip pytest, because invoking pytest without paths runs the whole suite.
206
206
  | --- | --- |
207
207
  | A transitive dependency | Import chains and package initialization |
208
208
  | A deleted or renamed module | Dependencies in both the base and head trees |
209
- | Fixtures or startup code | Ancestor conftests, plugins named by `-p` in `addopts`, and configured global modules |
209
+ | Fixtures or startup code | Ancestor conftests, plugins named by `-p` in `addopts`, the project's own `pytest11` entry points, and configured global modules |
210
210
  | Templates, schemas, or data files | [Reader-owned declarations and YAML sidecars](docs/tutorials/sidecar-dependencies.md) |
211
211
  | A surprisingly large selection | Dependency paths, startup-floor and blast-radius explanations |
212
212
  | A large CI suite | Cached parsing, graph reuse, and duration-based shard planning |
@@ -14,6 +14,11 @@ inputs:
14
14
  head:
15
15
  description: Exact checked-out commit.
16
16
  default: HEAD
17
+ pr-head:
18
+ description: The pull request's head. When the checkout is GitHub's merge of it (refs/pull/N/merge), changes are measured
19
+ from that merge's base-branch parent, so commits that landed on the base branch after `base` are not counted as the
20
+ PR's. Empty (any other event) keeps `base`.
21
+ default: ${{ github.event.pull_request.head.sha }}
17
22
  full-roots:
18
23
  description: Space-separated complete test roots used if planning cannot prove a narrower scope.
19
24
  required: true
@@ -100,6 +105,7 @@ runs:
100
105
  config: ${{ inputs.config }}
101
106
  base: ${{ inputs.base }}
102
107
  head: ${{ inputs.head }}
108
+ pr-head: ${{ inputs.pr-head }}
103
109
  full-roots: ${{ inputs.full-roots }}
104
110
  full: ${{ inputs.full }}
105
111
  extra-files: ${{ inputs.extra-files }}
@@ -7,6 +7,12 @@ inputs:
7
7
  base:
8
8
  description: Base commit containing the complete change set.
9
9
  default: ${{ github.event.pull_request.base.sha || github.event.before }}
10
+ pr-head:
11
+ description: >-
12
+ The pull request's head. When the checkout is GitHub's merge of it (refs/pull/N/merge), changes are measured from
13
+ that merge's base-branch parent, so commits that landed on the base branch after `base` are not counted as the PR's.
14
+ Empty (any other event) keeps `base`.
15
+ default: ${{ github.event.pull_request.head.sha }}
10
16
  full:
11
17
  description: 'auto preserves selection on PR/push reruns and runs full for manual/scheduled/release checkpoints or test:full. Override with true or false.'
12
18
  default: auto
@@ -40,10 +46,12 @@ runs:
40
46
  SELECTIVE_CONFIG: ${{ inputs.config }}
41
47
  SELECTIVE_BASE: ${{ inputs.base }}
42
48
  SELECTIVE_FULL: ${{ inputs.full }}
49
+ SELECTIVE_PR_HEAD: ${{ inputs.pr-head }}
43
50
  run: |
44
51
  export PYTHONPATH="$SELECTIVE_ACTION/../../src${PYTHONPATH:+:$PYTHONPATH}"
45
52
  args=(--config "$SELECTIVE_CONFIG" --base "$SELECTIVE_BASE" --github-output "$GITHUB_OUTPUT")
46
53
  args+=(--full "$SELECTIVE_FULL")
54
+ if [ -n "$SELECTIVE_PR_HEAD" ]; then args+=(--pr-head "$SELECTIVE_PR_HEAD"); fi
47
55
  uv run --no-project --python 3.14 --with pydantic==2.13.4 --with pyyaml==6.0.3 \
48
56
  python -m pytest_selective.ci "${args[@]}" | tee test-lanes.json
49
57
  {
@@ -13,6 +13,12 @@ inputs:
13
13
  head:
14
14
  description: Exact checked-out commit.
15
15
  default: HEAD
16
+ pr-head:
17
+ description: >-
18
+ The pull request's head. When the checkout is GitHub's merge of it (refs/pull/N/merge), changes are measured from
19
+ that merge's base-branch parent, so commits that landed on the base branch after `base` are not counted as the PR's.
20
+ Empty (any other event) keeps `base`.
21
+ default: ${{ github.event.pull_request.head.sha }}
16
22
  full-roots:
17
23
  description: Space-separated complete test roots used if planning cannot prove a narrower scope.
18
24
  required: true
@@ -114,6 +120,7 @@ runs:
114
120
  IMPACT_CONFIG: ${{ inputs.config }}
115
121
  IMPACT_BASE: ${{ inputs.base }}
116
122
  IMPACT_HEAD: ${{ inputs.head }}
123
+ IMPACT_PR_HEAD: ${{ inputs.pr-head }}
117
124
  IMPACT_FULL: ${{ inputs.full }}
118
125
  IMPACT_EXTRA: ${{ inputs.extra-files }}
119
126
  IMPACT_ALTERNATE_BASE: ${{ inputs.alternate-base }}
@@ -147,6 +154,7 @@ runs:
147
154
  # A reused workspace must not upload a previous plan's explanation.
148
155
  rm -f test-impact-explanation.json
149
156
  args+=(--full "$IMPACT_FULL")
157
+ if [ -n "${IMPACT_PR_HEAD:-}" ]; then args+=(--pr-head "$IMPACT_PR_HEAD"); fi
150
158
  if [ -n "$IMPACT_EXTRA" ]; then args+=(--extra-files "$IMPACT_EXTRA"); fi
151
159
  if [ -n "${IMPACT_GRAPH_BASE:-}" ]; then args+=(--graph-base "$IMPACT_GRAPH_BASE"); fi
152
160
  # Optional: an unfetchable alternate only forgoes a smaller selection.
@@ -305,7 +305,8 @@ class RepositoryDriver(SelectionDriver):
305
305
  changed = set(git_text(self.root, "diff", "--name-only", "--no-renames", base, self.head).splitlines())
306
306
  tests = self._tests(self.head)
307
307
  at_head = self._runtime(self.head, tests)
308
- at_base = self._runtime(base, [test for test in tests if test in set(self._tests(base))])
308
+ base_tests = set(self._tests(base))
309
+ at_base = self._runtime(base, [test for test in tests if test in base_tests])
309
310
  needed = {test for test in tests if (at_head[test] | at_base.get(test, frozenset())) & changed}
310
311
  return {NAMES[test] for test in needed} - set(selection["selected"])
311
312
 
@@ -9,12 +9,16 @@ Modules and sys.path are restored between test files, so each file's dependencie
9
9
  import json
10
10
  import os
11
11
  import sys
12
+ from functools import cache
12
13
  from pathlib import Path
13
14
 
14
15
  import pytest
15
16
 
16
17
 
18
+ @cache
17
19
  def _under(root: Path, path: str | None) -> str | None:
20
+ # Each process inspects one immutable exported tree. Cache path resolution,
21
+ # never the per-test import/open observations, which remain isolated below.
18
22
  if not path:
19
23
  return None
20
24
  try:
@@ -64,6 +64,14 @@ controls the test interpreter. `full-roots` must contain the complete roots, not
64
64
  the previous selection. `base` defaults to the PR base or the push's previous
65
65
  commit. Override it only when you have validated a different comparison base.
66
66
 
67
+ On a pull request, GitHub checks out a merge of the PR head into the base
68
+ branch's current tip, which can be newer than the PR's recorded base. Both
69
+ actions pass the PR head (`pr-head`) so the planner measures changes from that
70
+ merge's base-branch parent. Commits that landed on the base branch after the PR
71
+ was opened, such as a release's version bump, are then not counted as the PR's
72
+ own. A shallow checkout or any other event keeps `base`. Check out with
73
+ `fetch-depth: 0` to get the narrower plan.
74
+
67
75
  Both file and lane actions default to `full: auto`:
68
76
 
69
77
  | Trigger | Default |
@@ -22,6 +22,11 @@ select. The default filenames are `test_*.py`, `*_test.py`, `*_tests.py`, and
22
22
  `tests.py`; configure
23
23
  `test_patterns` for a different collection policy.
24
24
 
25
+ Every `--config` (`affected`, `explain`, `fingerprints`, `audit`, `run` and the rest) also accepts a
26
+ [lane config](ci.md) such as `.github/test-lanes.json`: it uses the lane config's `graph`, with its
27
+ top-level `global_inputs` when declared. Pass the lane config itself rather than a copy of its graph,
28
+ so a later `graph.pytest_policy` refresh is always the one in use.
29
+
25
30
  ## Review and record collection policy
26
31
 
27
32
  If your repository has `pyproject.toml`, `pytest.ini`, a `tox.ini` with a `[pytest]`
@@ -61,6 +66,9 @@ fixture example, directory manifests, dependency comments, and dynamic Python in
61
66
 
62
67
  A read that names a tracked file by literal path, such as `open("data/prices.json")` or
63
68
  `(Path(__file__).parent / "prices.json").read_text()`, is followed automatically as a content read.
69
+ A string that only names a tracked file, such as a routing test's `("bench.py", ...)` row, adds no
70
+ edge: a dotted string is followed as a possible module name (a patch target, a plugin) only when it
71
+ names no tracked file, from the repository root or beside its reader.
64
72
  For a path that is computed, or built from several parts, add a reader-owned comment:
65
73
 
66
74
  ```python
@@ -97,7 +105,7 @@ patterns to your actual inventory. Central-rule reader patterns use shell-style
97
105
  matching: here `*` also covers nested paths under `tests/`. Common lockfiles, dependency manifests and pytest configuration already belong
98
106
  to the default `global_inputs`. Keep those defaults unless you have reviewed a
99
107
  narrower environment contract; these reader-specific rules are for additional
100
- inputs. Other non-Python files are not automatically global inputs. Plugins named by `-p NAME` in a pytest configuration's `addopts` are global modules automatically; plugins passed only on the command line or in `PYTEST_ADDOPTS` are not visible to the planner. Use `global_modules` for those and other modules loaded by every test and
108
+ inputs. Other non-Python files are not automatically global inputs. Plugins named by `-p NAME` in a pytest configuration's `addopts` are global modules automatically, and so are the project's own `pytest11` entry points (`[project.entry-points.pytest11]` or `[tool.poetry.plugins.pytest11]` in any tracked `pyproject.toml`) whose module is a tracked file. An entry point stays global unless every pytest configuration disables it, with `-p no:NAME` (`NAME` is the entry point's name) or `--disable-plugin-autoload` in `addopts`. Plugins passed only on the command line or in `PYTEST_ADDOPTS`, and `PYTEST_DISABLE_PLUGIN_AUTOLOAD`, are not visible to the planner. Use `global_modules` for those and other modules loaded by every test and
101
109
  `always_run` for explicit canary test paths. Retain full-suite checkpoints.
102
110
 
103
111
  ## Explain and audit
@@ -121,6 +129,49 @@ pytest-selective audit --config .github/test-impact.json --records impact-audit
121
129
  Audit results cover executed paths, not every possible execution. Declare missing
122
130
  edges at their readers. See [evidence and limitations](evidence.md).
123
131
 
132
+ ### Write the missing declarations
133
+
134
+ `audit --write-declarations` writes a rule for every undeclared read into the
135
+ reading test's sidecar, `<test>.gv-dependency.yaml`, creating it or appending to
136
+ the one it already has:
137
+
138
+ ```bash
139
+ pytest-selective audit --config .github/test-impact.json --records impact-audit --write-declarations
140
+ pytest-selective audit --config .github/test-impact.json --records impact-audit --working-tree # confirms nothing is left before you commit
141
+ ```
142
+
143
+ ```yaml
144
+ dependencies:
145
+ - inputs:
146
+ - "//examples/backgrounds/**"
147
+ - "//examples/gold.toml"
148
+ kind: content
149
+ reason: "Read at runtime; recorded by pytest-selective audit --write-declarations."
150
+ - inputs:
151
+ - "//scripts/replay_coverage.py"
152
+ kind: import
153
+ reason: "Loaded or run outside the test's imports; recorded by pytest-selective audit --write-declarations."
154
+ ```
155
+
156
+ - A Python input becomes an `import` rule: a test that runs a script in a
157
+ subprocess also depends on what the script imports. Any other input becomes a
158
+ `content` rule. Review the kind where the test only reads Python as text.
159
+ - Several reads in one directory become `<directory>/**` only when the test read
160
+ every tracked file under it, so the glob claims nothing the run did not see.
161
+ It also covers files added there later.
162
+ - Every pattern must match a tracked file at the audited revision. A path the
163
+ pattern language cannot express (for example one containing `[`) is printed as
164
+ `not declared` and the command exits 2.
165
+ - Appending keeps an existing sidecar's text and comments. When the result would
166
+ not parse to exactly the old rules plus the new ones, the file is left alone
167
+ and the rules are printed for you to add by hand.
168
+
169
+ The command prints each sidecar and pattern it wrote and exits 0 when every
170
+ undeclared read was declared. The files are in the working tree, so the plain
171
+ audit (which reads `--head`) still reports them until you commit; use
172
+ `--working-tree` to check first. Prefer a hand-written comment or directory
173
+ manifest where it reads better; the writer only removes the transcription step.
174
+
124
175
  Audit discovery uses the requested repository even when a calling Git hook exports
125
176
  location variables for another checkout. It shares repository isolation with the
126
177
  planner and expansion guard.
@@ -30,7 +30,8 @@ uv run pytest-selective audit --config .github/test-impact.json --records impact
30
30
  The recorder observes real file reads in the tests you execute. The audit can
31
31
  expose a JSON fixture, template or schema that the graph did not declare. Add an
32
32
  edge at the reader using a dependency comment or YAML sidecar, then recheck the
33
- route. [Work through the sidecar tutorial](tutorials/sidecar-dependencies.md), or consult
33
+ route. `audit --write-declarations` writes those sidecars for you; see
34
+ [write the missing declarations](configuration.md#write-the-missing-declarations). [Work through the sidecar tutorial](tutorials/sidecar-dependencies.md), or consult
34
35
  [declaration syntax](configuration.md#declare-inputs-that-imports-cannot-describe).
35
36
  The audit covers observed paths; keep representative full runs for paths it has
36
37
  not seen, and explicitly model dynamic imports and external inputs.
@@ -0,0 +1,43 @@
1
+ # Plan once, execute across compatibility lanes
2
+
3
+ Use `pytest-selective execute-plan` to run a file plan emitted by the public
4
+ test-impact action. Every matrix job consumes the same plan; it does not need
5
+ Git history or a second dependency analysis.
6
+
7
+ ```yaml
8
+ - run: uv run --locked pytest-selective execute-plan -- -q -n 4 --dist=worksteal
9
+ env:
10
+ CI_PLANNER_RESULT: ${{ needs.plan.result }}
11
+ CI_TEST_MODE: ${{ needs.plan.outputs.mode }}
12
+ CI_TEST_FILES: ${{ needs.plan.outputs.files }}
13
+ CI_COVERAGE: 'true'
14
+ ```
15
+
16
+ `files` must be the action's `test_roots_json` output. A successful `none` plan
17
+ does not start pytest. Failed planning, malformed JSON, unknown modes or an
18
+ empty scoped selection execute the complete roots. Roots default to `tests`;
19
+ override with `--full-roots tests/unit tests/integration -- ...`. Paths with
20
+ spaces remain single arguments, and pytest receives its normal exit status.
21
+
22
+ `CI_COVERAGE=true` adds `--cov --cov-report=term-missing --cov-report=xml` only
23
+ for full execution. The environment must have pytest-cov installed. Keep
24
+ coverage thresholds in your project configuration; do not enforce a full-suite
25
+ threshold on selected files. The outer execution disables the selective plugin
26
+ because planning already happened. Integration tests can still load it explicitly.
27
+
28
+ When lane and file selection share a graph, derive the file config:
29
+
30
+ ```sh
31
+ pytest-selective file-config --lanes .github/test-lanes.json --output /tmp/test-impact.json
32
+ ```
33
+
34
+ This combines the graph's global inputs with the lane config's explicit global
35
+ inputs. Declare `global_inputs` before using it. Lane-specific `inputs` remain
36
+ lane admission rules; they do not automatically make every test file affected.
37
+ Declare non-import dependencies in the graph as usual.
38
+
39
+ pytest-selective's own CI uses four work-stealing workers on each existing
40
+ runner. Its full checkpoints retain all Python 3.11 and 3.14 compatibility tests
41
+ on Linux, Windows and macOS. Coverage runs on Python 3.14 on all three systems,
42
+ avoiding a second instrumentation pass on Python 3.11. The minimum-pytest lane,
43
+ Quint contract and installed-distribution smoke remain independent checks.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pytest-selective"
7
- version = "1.1.2"
7
+ version = "1.3.0"
8
8
  description = "Explainable Python test impact analysis: select affected pytest files from Git changes"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -4,15 +4,8 @@ import sys
4
4
  from pathlib import Path
5
5
 
6
6
  from pytest_selective.ci import LaneConfig
7
- from pytest_selective.impact_models import ImpactConfig
8
7
 
9
-
10
- def file_config(lanes: LaneConfig) -> ImpactConfig:
11
- if lanes.global_inputs is None:
12
- raise ValueError("This repository must declare its shared CI execution inputs")
13
- return lanes.graph.model_copy(
14
- update={"global_inputs": sorted(set(lanes.graph.global_inputs) | set(lanes.global_inputs))}
15
- )
8
+ file_config = LaneConfig.file_config
16
9
 
17
10
 
18
11
  if __name__ == "__main__":
@@ -0,0 +1,8 @@
1
+ """Compatibility entry point for the public shared-plan executor."""
2
+
3
+ import sys
4
+
5
+ from pytest_selective.execution import main
6
+
7
+ if __name__ == "__main__":
8
+ raise SystemExit(main(["--", *sys.argv[1:]]))