pytest-selective 1.2.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.2.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.2.0 → pytest_selective-1.4.0}/.github/workflows/ci.yml +19 -20
  4. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/pr-title.yml +2 -1
  5. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/public-actions.yml +6 -6
  6. pytest_selective-1.4.0/.github/workflows/weekly-checkpoint.yml +21 -0
  7. pytest_selective-1.4.0/.release-please-manifest.json +3 -0
  8. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/AGENTS.md +4 -3
  9. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/CHANGELOG.md +21 -0
  10. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/CONTRIBUTING.md +45 -20
  11. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/PKG-INFO +1 -1
  12. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/action.yml +6 -0
  13. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/actions/plan-lanes/action.yml +8 -0
  14. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/actions/test-impact/action.yml +8 -0
  15. pytest_selective-1.4.0/contracts/selection/conftest.py.gv-dependency.yaml +5 -0
  16. pytest_selective-1.4.0/contracts/selection/test_selection_contract.py.gv-dependency.yaml +6 -0
  17. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/ci-toolkit.md +8 -1
  18. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/ci.md +32 -0
  19. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/configuration.md +8 -0
  20. pytest_selective-1.4.0/documentation/case-study.md +293 -0
  21. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/mkdocs.yml +14 -0
  22. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/pyproject.toml +1 -1
  23. pytest_selective-1.4.0/scripts/check_lane_routing.py +145 -0
  24. pytest_selective-1.4.0/scripts/ci_file_config.py +18 -0
  25. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/smoke_distribution.py +2 -0
  26. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/skills/pytest-selective/SKILL.md +71 -18
  27. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/ci.py +39 -12
  28. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/git_operations.py +23 -0
  29. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_audit_declarations.py +13 -2
  30. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_cli.py +29 -4
  31. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_graph.py +47 -14
  32. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/local.py +3 -5
  33. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_ci_admission.py +2 -1
  34. pytest_selective-1.4.0/tests/test_ci_admission.py.gv-dependency.yaml +5 -0
  35. pytest_selective-1.4.0/tests/test_release_workflow.py.gv-dependency.yaml +5 -0
  36. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_repository_ci.py +88 -72
  37. pytest_selective-1.4.0/tests/test_repository_ci.py.gv-dependency.yaml +7 -0
  38. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_ci.py +76 -0
  39. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_impact_audit.py +7 -1
  40. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_impact_audit_declarations.py +10 -0
  41. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_impact_graph.py +83 -0
  42. pytest_selective-1.4.0/tests/testing/test_pull_request_merge_base.py +83 -0
  43. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_working_tree.py +10 -0
  44. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/uv.lock +1 -1
  45. pytest_selective-1.2.0/.release-please-manifest.json +0 -3
  46. pytest_selective-1.2.0/scripts/ci_file_config.py +0 -13
  47. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.dry-lint/baseline.json +0 -0
  48. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.dry-lint/progress.json +0 -0
  49. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.dry-lint.toml +0 -0
  50. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.gitattributes +0 -0
  51. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  52. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  53. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/dependabot.yml +0 -0
  54. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/validation-inputs.json +0 -0
  55. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/docs.yml +0 -0
  56. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/dry-lint-audit.yml +0 -0
  57. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/dry-lint-judge.yml +0 -0
  58. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/dry-lint.yml +0 -0
  59. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/observe-ci.yml +0 -0
  60. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/release-please.yml +0 -0
  61. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/release.yml +0 -0
  62. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.github/workflows/windows-performance.yml +0 -0
  63. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.gitignore +0 -0
  64. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.pre-commit-config.yaml +0 -0
  65. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/.readthedocs.yaml +0 -0
  66. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/CLAUDE.md +0 -0
  67. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/LICENSE +0 -0
  68. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/PROVENANCE.md +0 -0
  69. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/README.md +0 -0
  70. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/RELEASING.md +0 -0
  71. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/actions/README.md +0 -0
  72. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/actions/pytest-expansion/action.yml +0 -0
  73. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/actions/validation-gate/action.yml +0 -0
  74. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/agentsync.toml +0 -0
  75. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/contracts/selection/README.md +0 -0
  76. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/contracts/selection/conftest.py +0 -0
  77. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/contracts/selection/quint-connect.json +0 -0
  78. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/contracts/selection/quint.ini +0 -0
  79. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/contracts/selection/runtime_deps.py +0 -0
  80. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/contracts/selection/selection.qnt +0 -0
  81. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/contracts/selection/selection_driver.py +0 -0
  82. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/contracts/selection/test_selection_contract.py +0 -0
  83. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/README.md +0 -0
  84. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/actions.md +0 -0
  85. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/agents.md +0 -0
  86. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/assets/banner.png +0 -0
  87. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/assets/banner.svg +0 -0
  88. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/assets/brand.json +0 -0
  89. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/assets/icon.png +0 -0
  90. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/assets/logo.svg +0 -0
  91. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/assets/raster-sources.json +0 -0
  92. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/assets/social-preview.png +0 -0
  93. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/assets/social-preview.svg +0 -0
  94. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-due-ci.json +0 -0
  95. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-due-docs-ci.json +0 -0
  96. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-due-docs-widget.json +0 -0
  97. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-due-widget.json +0 -0
  98. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-input-replays.json +0 -0
  99. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-obligation-docs.json +0 -0
  100. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-obligation.json +0 -0
  101. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-routing.md +0 -0
  102. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22-selective-docs.json +0 -0
  103. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-22.md +0 -0
  104. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/obligation-baseline.json +0 -0
  105. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after-3.11.xml +0 -0
  106. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after-3.14.xml +0 -0
  107. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-after.json +0 -0
  108. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control-3.11.xml +0 -0
  109. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control-3.14.xml +0 -0
  110. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-control.json +0 -0
  111. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor/selective-earlier.json +0 -0
  112. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/2026-10-23-windows-saleor.md +0 -0
  113. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/benchmarks/windows-runtime.md +0 -0
  114. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/comparison-pytest-impacted.md +0 -0
  115. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/evidence.md +0 -0
  116. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/local-development.md +0 -0
  117. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/pytest-hygiene.md +0 -0
  118. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/selection-health.md +0 -0
  119. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/shared-plan-execution.md +0 -0
  120. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/tutorial.md +0 -0
  121. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/tutorials/adoption-checklist.md +0 -0
  122. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/tutorials/sidecar-dependencies.md +0 -0
  123. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/tutorials/skip-unrelated-ci.md +0 -0
  124. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/actions-directory-test-plan.md +0 -0
  125. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/codex-selector-correctness-performance-test-plan.md +0 -0
  126. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/codex-windows-git-performance-test-plan.md +0 -0
  127. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/graph-based-inputs-plan.md +0 -0
  128. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/impacted-improvements-test-plan.md +0 -0
  129. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/move-action-implementations-test-plan.md +0 -0
  130. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/public-action-onboarding-test-plan.md +0 -0
  131. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/ratchet-duplicates-zero-test-plan.md +0 -0
  132. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/docs/work-in-progress/tickets/rerun-selection-test-plan.md +0 -0
  133. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/assets/routing-comparison.svg +0 -0
  134. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/check_site.py +0 -0
  135. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/ecosystem.md +0 -0
  136. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/hooks.py +0 -0
  137. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/index.md +0 -0
  138. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/llms.txt +0 -0
  139. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/overrides/main.html +0 -0
  140. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/publishing.md +0 -0
  141. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/requirements.in +0 -0
  142. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/documentation/requirements.txt +0 -0
  143. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/release-please-config.json +0 -0
  144. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/benchmark_graph.py +0 -0
  145. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/benchmark_parsing.py +0 -0
  146. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/benchmark_rust.py +0 -0
  147. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/check_docs.py +0 -0
  148. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/check_metadata.py +0 -0
  149. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/compare_impacted.py +0 -0
  150. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/measure_ci_runs.py +0 -0
  151. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/public_actions.py +0 -0
  152. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/render_brand.py +0 -0
  153. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/run_ci_tests.py +0 -0
  154. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/scripts/tutorial.py +0 -0
  155. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/skills/pytest-selective/agents/openai.yaml +0 -0
  156. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/__init__.py +0 -0
  157. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/__main__.py +0 -0
  158. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/ast_imports.py +0 -0
  159. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/checkpoint.py +0 -0
  160. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/execution.py +0 -0
  161. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/gate.py +0 -0
  162. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/hygiene.py +0 -0
  163. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_audit.py +0 -0
  164. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_audit_plugin.py +0 -0
  165. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_declarations.py +0 -0
  166. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_explanation.py +0 -0
  167. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_first_party.py +0 -0
  168. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_models.py +0 -0
  169. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_name_scope.py +0 -0
  170. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/impact_selection.py +0 -0
  171. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/input_policy.py +0 -0
  172. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/parse_batch.py +0 -0
  173. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/plugin.py +0 -0
  174. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/py.typed +0 -0
  175. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/pytest_expansion.py +0 -0
  176. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/pytest_plugins.py +0 -0
  177. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/pytest_sharding.py +0 -0
  178. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/test_inventory.py +0 -0
  179. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/typecheck_scope.py +0 -0
  180. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/src/pytest_selective/working_tree.py +0 -0
  181. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/__init__.py +0 -0
  182. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_checkpoint_policy.py +0 -0
  183. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_ci_measurements.py +0 -0
  184. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_ci_test_execution.py +0 -0
  185. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_distribution_metadata.py +0 -0
  186. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_hygiene.py +0 -0
  187. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_public_actions.py +0 -0
  188. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_pytest_expansion.py +0 -0
  189. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_release_workflow.py +0 -0
  190. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_repository_hygiene.py +0 -0
  191. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_repository_hygiene.py.gv-dependency.yaml +0 -0
  192. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/test_tutorial.py +0 -0
  193. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/__init__.py +0 -0
  194. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/git_repo.py +0 -0
  195. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_ast_imports.py +0 -0
  196. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_gate.py +0 -0
  197. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_git_repo.py +0 -0
  198. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_global_inputs.py +0 -0
  199. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_impact_explanation.py +0 -0
  200. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_impact_first_party.py +0 -0
  201. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_impact_name_scope.py +0 -0
  202. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_parse_batch.py +0 -0
  203. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_portability.py +0 -0
  204. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_pytest_plugin.py +0 -0
  205. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_pytest_plugins.py +0 -0
  206. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_pytest_sharding.py +0 -0
  207. {pytest_selective-1.2.0 → pytest_selective-1.4.0}/tests/testing/test_typecheck_scope.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
+ }
@@ -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:
@@ -15,6 +13,9 @@ concurrency:
15
13
  group: ci-${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.run_attempt == 1 && github.event.pull_request.number || github.run_id }}
16
14
  cancel-in-progress: ${{ github.event_name == 'pull_request' && github.run_attempt == 1 }}
17
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.
18
19
  select-tests:
19
20
  runs-on: ubuntu-latest
20
21
  permissions:
@@ -33,7 +34,7 @@ jobs:
33
34
  CONTRACT_LANE_JOBS: '{"selection":"Selection contract (Quint)"}'
34
35
  outputs:
35
36
  # Planned lanes after reuse: a reusing lane is false only when every one of its jobs is proven by a receipt.
36
- lanes: ${{ steps.admit.outputs.lanes }}
37
+ lanes: ${{ steps.contract-evidence.outputs.admitted-lanes || steps.plan.outputs.lanes }}
37
38
  # The plan before reuse, for steps that follow changed runtime inputs rather than test execution.
38
39
  planned: ${{ steps.plan.outputs.lanes }}
39
40
  mode: ${{ steps.files.outputs.mode }}
@@ -51,9 +52,10 @@ jobs:
51
52
  if: github.event_name == 'pull_request'
52
53
  run: 'true'
53
54
  - id: plan
54
- uses: ./actions/plan-lanes
55
+ uses: gigaverse-app/ci-esta/actions/plan-work@v1
55
56
  with:
56
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.
57
59
  - name: Share the repository graph and execution inputs with file selection
58
60
  env:
59
61
  PYTHONPATH: src
@@ -84,6 +86,8 @@ jobs:
84
86
  input-paths: ${{ steps.inputs.outputs.tests }}
85
87
  reuse-after-merge: 'true'
86
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"]}'
87
91
  - id: contract-evidence
88
92
  if: >-
89
93
  ${{ (github.event_name == 'pull_request' || github.event_name == 'push') &&
@@ -95,21 +99,9 @@ jobs:
95
99
  input-paths: ${{ steps.inputs.outputs.contracts }}
96
100
  reuse-after-merge: 'true'
97
101
  reuse-version-only-release: 'true'
98
- - id: admit
99
- name: Admit each reusing lane unless every job is proven
100
- env:
101
- PLANNED_LANES: ${{ steps.plan.outputs.lanes }}
102
- REUSED: ${{ steps.evidence.outputs.reused || '{}' }}
103
- CONTRACT_REUSED: ${{ steps.contract-evidence.outputs.reused || '{}' }}
104
- run: |
105
- admitted=$(jq -c \
106
- --argjson tests "$TEST_LANE_JOBS" --argjson reused "$REUSED" \
107
- --argjson contracts "$CONTRACT_LANE_JOBS" --argjson contract_reused "$CONTRACT_REUSED" '
108
- def proven($jobs; $proof): [$jobs | keys[] | $proof[.] == true] | all;
109
- (if proven($tests; $reused) then .tests = false else . end)
110
- | (if proven($contracts; $contract_reused) then .contracts = false else . end)' <<<"$PLANNED_LANES")
111
- echo "reused=$REUSED contracts=$CONTRACT_REUSED"
112
- 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"]}'
113
105
  quality:
114
106
  runs-on: ubuntu-latest
115
107
  steps:
@@ -121,11 +113,18 @@ jobs:
121
113
  - run: uv run --locked pre-commit run --all-files
122
114
  env:
123
115
  SKIP: dry-lint
116
+ # Each lane-gated job's condition, the gate's lane-jobs and the lanes the workflow defines must agree.
124
117
  - name: Check aggregate gate against the execution lanes
125
118
  run: >-
126
119
  uv run --no-project --python 3.14
127
- --with "ci-esta>=1.1.0,<2.0.0"
120
+ --with "ci-esta[selection]>=1.13.0,<2.0.0"
128
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
129
128
  - name: Validate GitHub Actions
130
129
  run: docker run --rm -v "$PWD:/repo" -w /repo rhysd/actionlint:1.7.7 -color
131
130
  tests:
@@ -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:
@@ -17,10 +15,12 @@ jobs:
17
15
  - uses: actions/checkout@v7
18
16
  with:
19
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.
20
20
  - id: select
21
21
  uses: ./actions/plan-lanes
22
22
  with:
23
- config: .github/test-lanes.json
23
+ config: .github/plan-lanes.json
24
24
  artifact-name: public-action-selection
25
25
  public-actions:
26
26
  needs: select-tests
@@ -37,7 +37,7 @@ jobs:
37
37
  python - <<'PY'
38
38
  import json
39
39
  from pathlib import Path
40
- graph = json.loads(Path('.github/test-lanes.json').read_text())['graph']
40
+ graph = json.loads(Path('.github/plan-lanes.json').read_text())['graph']
41
41
  Path('smoke-impact.json').write_text(json.dumps(graph))
42
42
  PY
43
43
  - uses: ./
@@ -59,7 +59,7 @@ jobs:
59
59
  - uses: ./actions/plan-lanes
60
60
  id: lanes
61
61
  with:
62
- config: .github/test-lanes.json
62
+ config: .github/plan-lanes.json
63
63
  base: ${{ github.sha }}
64
64
  full: 'true'
65
65
  artifact-name: ''
@@ -70,7 +70,7 @@ jobs:
70
70
  python - <<'PY'
71
71
  import json, os
72
72
  # A full checkpoint admits every configured lane.
73
- 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']
74
74
  assert json.loads(os.environ['LANES']) == dict.fromkeys(configured, True)
75
75
  PY
76
76
  - name: Require successful planning after fallback execution
@@ -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.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,26 @@
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
+
10
+ ## [1.3.0](https://github.com/gigaverse-app/pytest-selective/compare/v1.2.0...v1.3.0) (2026-11-09)
11
+
12
+
13
+ ### Features
14
+
15
+ * **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))
16
+
17
+
18
+ ### Bug fixes
19
+
20
+ * 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))
21
+ * **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))
22
+ * **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))
23
+
3
24
  ## [1.2.0](https://github.com/gigaverse-app/pytest-selective/compare/v1.1.2...v1.2.0) (2026-11-09)
4
25
 
5
26
 
@@ -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)
@@ -199,14 +219,19 @@ history or failed planning must execute the full affected lanes and fail the
199
219
  required planning gate. Ordinary PRs and pushes use selection; manual/release/scheduled/full overrides
200
220
  remain complete checkpoints.
201
221
 
202
- CI and public-action smoke run full checkpoints every Monday. Ordinary runs keep
222
+ The **Weekly full checkpoint** workflow dispatches CI and public-action smoke
223
+ every Monday using [CI-esta's shared action](https://github.com/gigaverse-app/ci-esta/blob/main/docs/weekly-checkpoints.md).
224
+ Its small wrapper is generated by `ci-esta checkpoint init`; the source guide
225
+ documents schedule, workflow, ref and input overrides. Ordinary runs keep
203
226
  the shared affected-file plan across all six OS/Python combinations and minimum
204
227
  pytest. `tests/test_tutorial.py` owns the end-to-end tutorial execution and follows
205
228
  that plan; there is no second tutorial invocation in each matrix job. JUnit retains
206
229
  its test result and timing, and full plans also upload whole-suite coverage.
207
230
 
208
- To run a complete checkpoint after a CI change, select **CI** in GitHub Actions
209
- and choose **Run workflow**. Retrying an ordinary PR run preserves its selection;
231
+ To run both complete checkpoints after a CI change, select **Weekly full checkpoint**
232
+ in GitHub Actions and choose **Run workflow**, then inspect the dispatched CI and
233
+ public-action results. A green dispatcher means both requests were accepted.
234
+ You can also dispatch either target directly. Retrying an ordinary PR run preserves its selection;
210
235
  use the manual checkpoint when you need full-suite coverage evidence.
211
236
 
212
237
  ## Duplication
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pytest-selective
3
- Version: 1.2.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/
@@ -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.
@@ -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
@@ -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 |
@@ -277,3 +285,27 @@ The action also uploads the report as the `test-lane-selection` artifact; set
277
285
  pull-request sticky's lane section: which lanes ran and why, and the runner time
278
286
  the skipped jobs took on the default branch's last full run. See
279
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.
@@ -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