pytest-selective 1.3.0__tar.gz → 1.4.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 (207) hide show
  1. pytest_selective-1.3.0/.github/test-lanes.json → pytest_selective-1.4.0/.github/plan-lanes.json +1 -21
  2. pytest_selective-1.4.0/.github/test-lanes.json +22 -0
  3. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.github/workflows/ci.yml +19 -18
  4. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.github/workflows/public-actions.yml +6 -4
  5. pytest_selective-1.4.0/.release-please-manifest.json +3 -0
  6. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/AGENTS.md +4 -3
  7. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/CHANGELOG.md +7 -0
  8. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/CONTRIBUTING.md +37 -17
  9. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/PKG-INFO +1 -1
  10. pytest_selective-1.4.0/contracts/selection/conftest.py.gv-dependency.yaml +5 -0
  11. pytest_selective-1.4.0/contracts/selection/test_selection_contract.py.gv-dependency.yaml +6 -0
  12. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/ci-toolkit.md +8 -1
  13. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/ci.md +24 -0
  14. pytest_selective-1.4.0/documentation/case-study.md +293 -0
  15. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/mkdocs.yml +14 -0
  16. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/pyproject.toml +1 -1
  17. pytest_selective-1.4.0/scripts/check_lane_routing.py +145 -0
  18. pytest_selective-1.4.0/scripts/ci_file_config.py +18 -0
  19. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/scripts/smoke_distribution.py +2 -0
  20. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/ci.py +15 -11
  21. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_graph.py +11 -3
  22. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/test_ci_admission.py +2 -1
  23. pytest_selective-1.4.0/tests/test_ci_admission.py.gv-dependency.yaml +5 -0
  24. pytest_selective-1.4.0/tests/test_release_workflow.py.gv-dependency.yaml +5 -0
  25. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/test_repository_ci.py +76 -71
  26. pytest_selective-1.4.0/tests/test_repository_ci.py.gv-dependency.yaml +7 -0
  27. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_ci.py +76 -0
  28. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/uv.lock +1 -1
  29. pytest_selective-1.3.0/.release-please-manifest.json +0 -3
  30. pytest_selective-1.3.0/scripts/ci_file_config.py +0 -13
  31. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.dry-lint/baseline.json +0 -0
  32. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.dry-lint/progress.json +0 -0
  33. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.dry-lint.toml +0 -0
  34. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.gitattributes +0 -0
  35. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  36. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  37. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.github/dependabot.yml +0 -0
  38. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.github/validation-inputs.json +0 -0
  39. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.github/workflows/docs.yml +0 -0
  40. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.github/workflows/dry-lint-audit.yml +0 -0
  41. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.github/workflows/dry-lint-judge.yml +0 -0
  42. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.github/workflows/dry-lint.yml +0 -0
  43. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.github/workflows/observe-ci.yml +0 -0
  44. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.github/workflows/pr-title.yml +0 -0
  45. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.github/workflows/release-please.yml +0 -0
  46. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.github/workflows/release.yml +0 -0
  47. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.github/workflows/weekly-checkpoint.yml +0 -0
  48. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.github/workflows/windows-performance.yml +0 -0
  49. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.gitignore +0 -0
  50. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.pre-commit-config.yaml +0 -0
  51. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/.readthedocs.yaml +0 -0
  52. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/CLAUDE.md +0 -0
  53. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/LICENSE +0 -0
  54. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/PROVENANCE.md +0 -0
  55. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/README.md +0 -0
  56. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/RELEASING.md +0 -0
  57. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/action.yml +0 -0
  58. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/actions/README.md +0 -0
  59. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/actions/plan-lanes/action.yml +0 -0
  60. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/actions/pytest-expansion/action.yml +0 -0
  61. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/actions/test-impact/action.yml +0 -0
  62. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/actions/validation-gate/action.yml +0 -0
  63. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/agentsync.toml +0 -0
  64. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/contracts/selection/README.md +0 -0
  65. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/contracts/selection/conftest.py +0 -0
  66. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/contracts/selection/quint-connect.json +0 -0
  67. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/contracts/selection/quint.ini +0 -0
  68. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/contracts/selection/runtime_deps.py +0 -0
  69. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/contracts/selection/selection.qnt +0 -0
  70. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/contracts/selection/selection_driver.py +0 -0
  71. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/contracts/selection/test_selection_contract.py +0 -0
  72. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/README.md +0 -0
  73. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/actions.md +0 -0
  74. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/agents.md +0 -0
  75. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/assets/banner.png +0 -0
  76. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/assets/banner.svg +0 -0
  77. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/assets/brand.json +0 -0
  78. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/assets/icon.png +0 -0
  79. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/assets/logo.svg +0 -0
  80. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/assets/raster-sources.json +0 -0
  81. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/assets/social-preview.png +0 -0
  82. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/assets/social-preview.svg +0 -0
  83. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-due-ci.json +0 -0
  84. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-due-docs-ci.json +0 -0
  85. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-due-docs-widget.json +0 -0
  86. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-due-widget.json +0 -0
  87. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-input-replays.json +0 -0
  88. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-obligation-docs.json +0 -0
  89. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-obligation.json +0 -0
  90. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-routing.md +0 -0
  91. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-selective-docs.json +0 -0
  92. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22.md +0 -0
  93. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/obligation-baseline.json +0 -0
  94. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after-3.11.xml +0 -0
  95. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after-3.14.xml +0 -0
  96. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after.json +0 -0
  97. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control-3.11.xml +0 -0
  98. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control-3.14.xml +0 -0
  99. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control.json +0 -0
  100. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-earlier.json +0 -0
  101. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor.md +0 -0
  102. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/benchmarks/windows-runtime.md +0 -0
  103. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/comparison-pytest-impacted.md +0 -0
  104. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/configuration.md +0 -0
  105. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/evidence.md +0 -0
  106. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/local-development.md +0 -0
  107. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/pytest-hygiene.md +0 -0
  108. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/selection-health.md +0 -0
  109. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/shared-plan-execution.md +0 -0
  110. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/tutorial.md +0 -0
  111. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/tutorials/adoption-checklist.md +0 -0
  112. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/tutorials/sidecar-dependencies.md +0 -0
  113. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/tutorials/skip-unrelated-ci.md +0 -0
  114. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/actions-directory-test-plan.md +0 -0
  115. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/codex-selector-correctness-performance-test-plan.md +0 -0
  116. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/codex-windows-git-performance-test-plan.md +0 -0
  117. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/graph-based-inputs-plan.md +0 -0
  118. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/impacted-improvements-test-plan.md +0 -0
  119. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/move-action-implementations-test-plan.md +0 -0
  120. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/public-action-onboarding-test-plan.md +0 -0
  121. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/ratchet-duplicates-zero-test-plan.md +0 -0
  122. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/rerun-selection-test-plan.md +0 -0
  123. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/documentation/assets/routing-comparison.svg +0 -0
  124. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/documentation/check_site.py +0 -0
  125. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/documentation/ecosystem.md +0 -0
  126. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/documentation/hooks.py +0 -0
  127. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/documentation/index.md +0 -0
  128. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/documentation/llms.txt +0 -0
  129. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/documentation/overrides/main.html +0 -0
  130. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/documentation/publishing.md +0 -0
  131. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/documentation/requirements.in +0 -0
  132. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/documentation/requirements.txt +0 -0
  133. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/release-please-config.json +0 -0
  134. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/scripts/benchmark_graph.py +0 -0
  135. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/scripts/benchmark_parsing.py +0 -0
  136. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/scripts/benchmark_rust.py +0 -0
  137. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/scripts/check_docs.py +0 -0
  138. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/scripts/check_metadata.py +0 -0
  139. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/scripts/compare_impacted.py +0 -0
  140. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/scripts/measure_ci_runs.py +0 -0
  141. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/scripts/public_actions.py +0 -0
  142. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/scripts/render_brand.py +0 -0
  143. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/scripts/run_ci_tests.py +0 -0
  144. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/scripts/tutorial.py +0 -0
  145. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/skills/pytest-selective/SKILL.md +0 -0
  146. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/skills/pytest-selective/agents/openai.yaml +0 -0
  147. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/__init__.py +0 -0
  148. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/__main__.py +0 -0
  149. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/ast_imports.py +0 -0
  150. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/checkpoint.py +0 -0
  151. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/execution.py +0 -0
  152. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/gate.py +0 -0
  153. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/git_operations.py +0 -0
  154. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/hygiene.py +0 -0
  155. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_audit.py +0 -0
  156. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_audit_declarations.py +0 -0
  157. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_audit_plugin.py +0 -0
  158. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_cli.py +0 -0
  159. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_declarations.py +0 -0
  160. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_explanation.py +0 -0
  161. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_first_party.py +0 -0
  162. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_models.py +0 -0
  163. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_name_scope.py +0 -0
  164. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_selection.py +0 -0
  165. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/input_policy.py +0 -0
  166. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/local.py +0 -0
  167. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/parse_batch.py +0 -0
  168. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/plugin.py +0 -0
  169. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/py.typed +0 -0
  170. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/pytest_expansion.py +0 -0
  171. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/pytest_plugins.py +0 -0
  172. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/pytest_sharding.py +0 -0
  173. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/test_inventory.py +0 -0
  174. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/typecheck_scope.py +0 -0
  175. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/src/pytest_selective/working_tree.py +0 -0
  176. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/__init__.py +0 -0
  177. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/test_checkpoint_policy.py +0 -0
  178. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/test_ci_measurements.py +0 -0
  179. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/test_ci_test_execution.py +0 -0
  180. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/test_distribution_metadata.py +0 -0
  181. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/test_hygiene.py +0 -0
  182. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/test_public_actions.py +0 -0
  183. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/test_pytest_expansion.py +0 -0
  184. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/test_release_workflow.py +0 -0
  185. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/test_repository_hygiene.py +0 -0
  186. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/test_repository_hygiene.py.gv-dependency.yaml +0 -0
  187. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/test_tutorial.py +0 -0
  188. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/__init__.py +0 -0
  189. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/git_repo.py +0 -0
  190. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_ast_imports.py +0 -0
  191. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_gate.py +0 -0
  192. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_git_repo.py +0 -0
  193. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_global_inputs.py +0 -0
  194. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_impact_audit.py +0 -0
  195. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_impact_audit_declarations.py +0 -0
  196. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_impact_explanation.py +0 -0
  197. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_impact_first_party.py +0 -0
  198. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_impact_graph.py +0 -0
  199. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_impact_name_scope.py +0 -0
  200. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_parse_batch.py +0 -0
  201. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_portability.py +0 -0
  202. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_pull_request_merge_base.py +0 -0
  203. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_pytest_plugin.py +0 -0
  204. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_pytest_plugins.py +0 -0
  205. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_pytest_sharding.py +0 -0
  206. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_typecheck_scope.py +0 -0
  207. {pytest_selective-1.3.0 → pytest_selective-1.4.0}/tests/testing/test_working_tree.py +0 -0
@@ -25,27 +25,6 @@
25
25
  "src/**",
26
26
  "tests/**"
27
27
  ]
28
- },
29
- "contracts": {
30
- "roots": [
31
- "contracts"
32
- ],
33
- "inputs": [
34
- "src/**",
35
- "contracts/**",
36
- "tests/testing/git_repo.py"
37
- ]
38
- },
39
- "package": {
40
- "inputs": [
41
- "src/**",
42
- "pyproject.toml",
43
- "uv.lock",
44
- "README.md",
45
- "LICENSE",
46
- "scripts/check_metadata.py",
47
- "scripts/smoke_distribution.py"
48
- ]
49
28
  }
50
29
  },
51
30
  "global_inputs": [
@@ -56,6 +35,7 @@
56
35
  "pyproject.toml",
57
36
  "**/uv.lock",
58
37
  "uv.lock",
38
+ ".github/plan-lanes.json",
59
39
  ".github/test-lanes.json",
60
40
  ".github/workflows/ci.yml",
61
41
  ".github/workflows/release.yml",
@@ -0,0 +1,22 @@
1
+ {
2
+ "extends": "workflow",
3
+ "lanes": {
4
+ "tests": {
5
+ "roots": ["tests"]
6
+ }
7
+ },
8
+ "rules": [
9
+ {
10
+ "name": "python-environment",
11
+ "when": ["pyproject.toml", "**/pyproject.toml", "uv.lock", "**/uv.lock", ".python-version"],
12
+ "select": ["all"],
13
+ "reason": "the locked test environment; documentation/requirements*.txt belongs to Read the Docs"
14
+ },
15
+ {
16
+ "name": "distribution-smoke",
17
+ "when": ["scripts/smoke_distribution.py"],
18
+ "select": ["lane:tests"],
19
+ "reason": "the distributions job runs the smoke only when the tests lane is planned"
20
+ }
21
+ ]
22
+ }
@@ -13,6 +13,9 @@ concurrency:
13
13
  group: ci-${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.run_attempt == 1 && github.event.pull_request.number || github.run_id }}
14
14
  cancel-in-progress: ${{ github.event_name == 'pull_request' && github.run_attempt == 1 }}
15
15
  jobs:
16
+ # Plans whole lanes before any runner starts: each lane is the name a job's condition reads, and a change selects it
17
+ # through the tests, scripts and actions that job runs. `ci-esta lanes` shows the lanes and why; .github/test-lanes.json
18
+ # holds only what the workflow cannot show.
16
19
  select-tests:
17
20
  runs-on: ubuntu-latest
18
21
  permissions:
@@ -31,7 +34,7 @@ jobs:
31
34
  CONTRACT_LANE_JOBS: '{"selection":"Selection contract (Quint)"}'
32
35
  outputs:
33
36
  # Planned lanes after reuse: a reusing lane is false only when every one of its jobs is proven by a receipt.
34
- lanes: ${{ steps.admit.outputs.lanes }}
37
+ lanes: ${{ steps.contract-evidence.outputs.admitted-lanes || steps.plan.outputs.lanes }}
35
38
  # The plan before reuse, for steps that follow changed runtime inputs rather than test execution.
36
39
  planned: ${{ steps.plan.outputs.lanes }}
37
40
  mode: ${{ steps.files.outputs.mode }}
@@ -49,9 +52,10 @@ jobs:
49
52
  if: github.event_name == 'pull_request'
50
53
  run: 'true'
51
54
  - id: plan
52
- uses: ./actions/plan-lanes
55
+ uses: gigaverse-app/ci-esta/actions/plan-work@v1
53
56
  with:
54
57
  config: .github/test-lanes.json
58
+ # File selection has no workflow graph, so it reads the complete lane file public-actions.yml plans with.
55
59
  - name: Share the repository graph and execution inputs with file selection
56
60
  env:
57
61
  PYTHONPATH: src
@@ -82,6 +86,8 @@ jobs:
82
86
  input-paths: ${{ steps.inputs.outputs.tests }}
83
87
  reuse-after-merge: 'true'
84
88
  reuse-version-only-release: 'true'
89
+ planned-lanes: ${{ steps.plan.outputs.lanes }}
90
+ lane-keys: '{"tests":["py311-ubuntu","py311-windows","py311-macos","py314-ubuntu","py314-windows","py314-macos","minimum"]}'
85
91
  - id: contract-evidence
86
92
  if: >-
87
93
  ${{ (github.event_name == 'pull_request' || github.event_name == 'push') &&
@@ -93,21 +99,9 @@ jobs:
93
99
  input-paths: ${{ steps.inputs.outputs.contracts }}
94
100
  reuse-after-merge: 'true'
95
101
  reuse-version-only-release: 'true'
96
- - id: admit
97
- name: Admit each reusing lane unless every job is proven
98
- env:
99
- PLANNED_LANES: ${{ steps.plan.outputs.lanes }}
100
- REUSED: ${{ steps.evidence.outputs.reused || '{}' }}
101
- CONTRACT_REUSED: ${{ steps.contract-evidence.outputs.reused || '{}' }}
102
- run: |
103
- admitted=$(jq -c \
104
- --argjson tests "$TEST_LANE_JOBS" --argjson reused "$REUSED" \
105
- --argjson contracts "$CONTRACT_LANE_JOBS" --argjson contract_reused "$CONTRACT_REUSED" '
106
- def proven($jobs; $proof): [$jobs | keys[] | $proof[.] == true] | all;
107
- (if proven($tests; $reused) then .tests = false else . end)
108
- | (if proven($contracts; $contract_reused) then .contracts = false else . end)' <<<"$PLANNED_LANES")
109
- echo "reused=$REUSED contracts=$CONTRACT_REUSED"
110
- echo "lanes=$admitted" | tee -a "$GITHUB_OUTPUT"
102
+ # Chained: the tests step already admitted its lane; this step admits the contracts lane on top.
103
+ planned-lanes: ${{ steps.evidence.outputs.admitted-lanes }}
104
+ lane-keys: '{"contracts":["selection"]}'
111
105
  quality:
112
106
  runs-on: ubuntu-latest
113
107
  steps:
@@ -119,11 +113,18 @@ jobs:
119
113
  - run: uv run --locked pre-commit run --all-files
120
114
  env:
121
115
  SKIP: dry-lint
116
+ # Each lane-gated job's condition, the gate's lane-jobs and the lanes the workflow defines must agree.
122
117
  - name: Check aggregate gate against the execution lanes
123
118
  run: >-
124
119
  uv run --no-project --python 3.14
125
- --with "ci-esta>=1.1.0,<2.0.0"
120
+ --with "ci-esta[selection]>=1.13.0,<2.0.0"
126
121
  ci-esta check-workflow --workflow .github/workflows/ci.yml --lanes .github/test-lanes.json
122
+ # Plans one-file changes with CI-esta against real commits and fails on any lane a change should not run.
123
+ - name: Check the lane routing
124
+ run: >-
125
+ uv run --no-project --python 3.14
126
+ --with "ci-esta[selection]>=1.13.0,<2.0.0"
127
+ python scripts/check_lane_routing.py
127
128
  - name: Validate GitHub Actions
128
129
  run: docker run --rm -v "$PWD:/repo" -w /repo rhysd/actionlint:1.7.7 -color
129
130
  tests:
@@ -15,10 +15,12 @@ jobs:
15
15
  - uses: actions/checkout@v7
16
16
  with:
17
17
  fetch-depth: 0
18
+ # This workflow smokes the repository's own actions, so its planner is plan-lanes itself, run selectively on every
19
+ # change against .github/plan-lanes.json; CI's lanes come from CI-esta's plan-work.
18
20
  - id: select
19
21
  uses: ./actions/plan-lanes
20
22
  with:
21
- config: .github/test-lanes.json
23
+ config: .github/plan-lanes.json
22
24
  artifact-name: public-action-selection
23
25
  public-actions:
24
26
  needs: select-tests
@@ -35,7 +37,7 @@ jobs:
35
37
  python - <<'PY'
36
38
  import json
37
39
  from pathlib import Path
38
- graph = json.loads(Path('.github/test-lanes.json').read_text())['graph']
40
+ graph = json.loads(Path('.github/plan-lanes.json').read_text())['graph']
39
41
  Path('smoke-impact.json').write_text(json.dumps(graph))
40
42
  PY
41
43
  - uses: ./
@@ -57,7 +59,7 @@ jobs:
57
59
  - uses: ./actions/plan-lanes
58
60
  id: lanes
59
61
  with:
60
- config: .github/test-lanes.json
62
+ config: .github/plan-lanes.json
61
63
  base: ${{ github.sha }}
62
64
  full: 'true'
63
65
  artifact-name: ''
@@ -68,7 +70,7 @@ jobs:
68
70
  python - <<'PY'
69
71
  import json, os
70
72
  # A full checkpoint admits every configured lane.
71
- configured = json.load(open('.github/test-lanes.json', encoding='utf-8'))['lanes']
73
+ configured = json.load(open('.github/plan-lanes.json', encoding='utf-8'))['lanes']
72
74
  assert json.loads(os.environ['LANES']) == dict.fromkeys(configured, True)
73
75
  PY
74
76
  - name: Require successful planning after fallback execution
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.4.0"
3
+ }
@@ -19,9 +19,10 @@ on pytest-selective itself.
19
19
  - Planning supports committed revisions and explicit working-tree snapshots. Preserve full execution on
20
20
  operational uncertainty and diagnostics for invalid declarations. An empty
21
21
  selection is not an empty pytest argument list.
22
- - Select consumer routing regressions by their declared execution/configuration inputs. Update
23
- `.github/test-lanes.json` when adding execution inputs; preserve explicit full/manual
24
- checkpoints and the `test:full` override.
22
+ - Select consumer routing regressions by their declared execution/configuration inputs. Declare a
23
+ file a test or CI script reads by path next to its reader; `.github/test-lanes.json` holds only
24
+ what CI-esta cannot see in the workflow, and `scripts/check_lane_routing.py` pins the lanes. Preserve
25
+ explicit full/manual checkpoints and the `test:full` override.
25
26
  - The README's performance numbers are scenario-specific observations. Keep
26
27
  claims linked to [raw evidence](docs/benchmarks/2026-10-22-routing.md); distinguish
27
28
  runner-seconds, elapsed time and billing. Skill/example edits need usable links
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.4.0](https://github.com/gigaverse-app/pytest-selective/compare/v1.3.0...v1.4.0) (2026-11-09)
4
+
5
+
6
+ ### Features
7
+
8
+ * **lanes:** partition test roots by filename patterns ([27c10ec](https://github.com/gigaverse-app/pytest-selective/commit/27c10ec608f052a36f58d9b10730048ab9d38598))
9
+
3
10
  ## [1.3.0](https://github.com/gigaverse-app/pytest-selective/compare/v1.2.0...v1.3.0) (2026-11-09)
4
11
 
5
12
 
@@ -98,24 +98,44 @@ limitations. The pull request template provides a short checklist. See
98
98
 
99
99
  ## CI lane selection
100
100
 
101
- This repository uses its own `plan-lanes` action. Documentation-only PRs keep
101
+ CI plans whole lanes with [CI-esta](https://github.com/gigaverse-app/ci-esta)'s
102
+ `plan-work` before any runner starts: `tests` (the Python matrix and
103
+ `pytest-minimum`), `contracts` (the Quint selection contract) and `package` (the
104
+ distribution build), the names the jobs' conditions read. A change selects a lane
105
+ through what its jobs run: the tests that import it, the files tests declare they
106
+ read, and the scripts and actions each job executes. Documentation-only PRs keep
102
107
  quality checks (including Markdown links) and the documentation build, while
103
- skipping every test lane and the distribution build. A README or LICENSE change
104
- also runs the `package` lane, because both distributions embed them. Within an
105
- admitted lane, the repository's public file-selection action selects affected
106
- test files once; the platform matrix and minimum-pytest job execute that same
107
- plan. CI-esta receives both lane and file explanations, learns JUnit timings,
108
- and validates the aggregate gate. Source and test edits follow dependencies;
109
- scripts, dependency/pytest policy and execution workflow changes retain full
110
- validation. Manual/called/release runs and the `test:full` label run it in full; a
111
- failed planner also runs all tests. Installed-distribution smoke checks use the
112
- same runtime-input decision; README-only changes skip those checks while still
113
- verifying the README embedded in both release archives. Repository routing
114
- controls exercise the committed config against real Git trees, including full and missing-history
115
- controls. Add new non-import execution inputs to `.github/test-lanes.json`.
116
-
117
- File planning derives its graph from that same lane config and merges its declared
118
- global execution inputs with the graph's environment inputs. Declare subprocess
108
+ skipping every lane. A README or LICENSE change runs the `package` lane, because
109
+ both distributions embed them; a README change also runs `contracts`, whose job
110
+ checks the README's contract report, and the one test whose metadata check reads it. `pyproject.toml` and `uv.lock` run every
111
+ lane. `ci-esta lanes --base origin/main` shows what a change would run and why.
112
+ [.github/test-lanes.json](.github/test-lanes.json) extends the workflow and holds
113
+ only what it cannot show: the `tests` lane's roots (its jobs run `pytest-selective
114
+ execute-plan`, which CI-esta does not read as a pytest command), a
115
+ `python-environment` rule that leaves the documentation site's requirements out,
116
+ and a rule that plans the tests lane when the installed-distribution smoke script
117
+ changes, because `distributions` smokes only when that lane is planned. When a test
118
+ or a script starts reading a file by path, declare it next to the reader (a
119
+ `<test>.gv-dependency.yaml` sidecar, which `pytest-selective audit
120
+ --write-declarations` writes, or a `# gv:test-dependency: PATH` comment).
121
+ `quality` runs [scripts/check_lane_routing.py](scripts/check_lane_routing.py),
122
+ which plans one-file changes with CI-esta against real Git commits, including a
123
+ full checkpoint, and `ci-esta check-workflow`, which keeps the job conditions, the
124
+ gate and the lane file in agreement. Manual/called/release runs and the
125
+ `test:full` label run everything; a failed planner also runs all tests.
126
+ Installed-distribution smoke checks follow the tests lane's plan; README-only
127
+ changes skip them while still verifying the README embedded in both release
128
+ archives.
129
+
130
+ Within an admitted lane, the repository's public file-selection action selects
131
+ affected test files once; the platform matrix and minimum-pytest job execute that
132
+ same plan. CI-esta receives both lane and file explanations, learns JUnit timings,
133
+ and validates the aggregate gate. File selection has no workflow graph, so it reads
134
+ [.github/plan-lanes.json](.github/plan-lanes.json), a complete lane file: its graph
135
+ plus the execution inputs that run every test file (scripts, workflows, actions,
136
+ dependency and pytest policy). `public-actions.yml` plans with the same file through
137
+ this repository's own `plan-lanes` action, selectively on every change, and smokes
138
+ its full checkpoint. Declare subprocess
119
139
  and non-import dependencies at their readers. Full plans collect whole-suite
120
140
  coverage; scoped plans produce JUnit timing evidence without presenting partial
121
141
  coverage as a whole-suite result. The public [shared-plan executor](docs/shared-plan-execution.md)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pytest-selective
3
- Version: 1.3.0
3
+ Version: 1.4.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/
@@ -0,0 +1,5 @@
1
+ dependencies:
2
+ - inputs:
3
+ - "//contracts/selection/runtime_deps.py"
4
+ kind: import
5
+ reason: "The runtime referee, run by path in a subprocess (python -I -B runtime_deps.py) for every history."
@@ -0,0 +1,6 @@
1
+ dependencies:
2
+ - inputs:
3
+ - "//contracts/selection/quint-connect.json"
4
+ - "//contracts/selection/selection.qnt"
5
+ kind: content
6
+ reason: "quint_contract() reads the contract file, which names the Quint model it runs."
@@ -36,7 +36,14 @@ PR, on a version-only release PR, and on a PR push that changes nothing the
36
36
  tests read. A `PR evidence #N head SHA` step ties each PR run to its pull
37
37
  request, because GitHub often leaves that link out. The distribution smoke test
38
38
  still follows the plan before reuse (`planned`), and partial proof runs the
39
- whole lane.
39
+ whole lane. The contract lane works the same way with its own receipt and inputs
40
+ (including `README.md`, which its README check reads).
41
+
42
+ CI-esta decides admission: each evidence step gets the planned lanes and
43
+ `lane-keys`, the keys of every job a lane gates, and reports `admitted-lanes`.
44
+ The contract step chains on the tests step's result. `ci-esta check-workflow`
45
+ then proves `lane-keys` name every job each lane gates, matrix cells included,
46
+ so a newly added cell can never be skipped as proven.
40
47
 
41
48
  The unconditional CI job runs `ci-esta check-workflow` against this workflow and
42
49
  lane configuration, so renamed lanes or omitted metadata prerequisites cannot
@@ -285,3 +285,27 @@ The action also uploads the report as the `test-lane-selection` artifact; set
285
285
  pull-request sticky's lane section: which lanes ran and why, and the runner time
286
286
  the skipped jobs took on the default branch's last full run. See
287
287
  [how the two fit together in a Python project](https://github.com/gigaverse-app/ci-esta/blob/main/docs/integrations.md#getting-the-most-from-both-in-a-python-project).
288
+
289
+ ## Split whole lanes by test filename
290
+
291
+ Lanes sharing a directory can partition its tests with `test_patterns` and
292
+ `exclude_patterns`. Both match repository-relative POSIX paths using the same
293
+ globs as lane inputs; `*` also matches path separators. Roots still limit the
294
+ search. The defaults include every test under the roots and exclude none.
295
+
296
+ ```json
297
+ {
298
+ "unit": {"roots": ["tests"], "exclude_patterns": ["*_integration.py"]},
299
+ "integration": {"roots": ["tests"], "test_patterns": ["*_integration.py"]}
300
+ }
301
+ ```
302
+
303
+ Put these entries under `lanes` in the lane configuration. A source dependency
304
+ still selects every lane containing an affected test. Deleted tests use their
305
+ base-revision membership; an affected test claimed by no lane causes a full
306
+ fallback. Explicit inputs and full checkpoints are unaffected by these filters.
307
+
308
+ Same-named modules in independent module roots are unrelated unless an import
309
+ connects them. An actual ambiguous bare import conservatively retains every
310
+ possible target. Use qualified imports to make independent demo modules
311
+ unambiguous; selection does not guess a runtime import order.