pytest-selective 0.4.0__tar.gz → 0.4.2__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 (180) hide show
  1. pytest_selective-0.4.2/.dry-lint/baseline.json +80 -0
  2. pytest_selective-0.4.2/.dry-lint/progress.json +7 -0
  3. pytest_selective-0.4.2/.dry-lint.toml +6 -0
  4. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/.github/workflows/ci.yml +10 -7
  5. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/.github/workflows/docs.yml +3 -3
  6. pytest_selective-0.4.2/.github/workflows/dry-lint-audit.yml +91 -0
  7. pytest_selective-0.4.2/.github/workflows/dry-lint-judge.yml +30 -0
  8. pytest_selective-0.4.2/.github/workflows/dry-lint.yml +41 -0
  9. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/.github/workflows/pr-title.yml +1 -1
  10. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/.github/workflows/public-actions.yml +1 -1
  11. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/.github/workflows/release-please.yml +2 -2
  12. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/.github/workflows/release.yml +4 -4
  13. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/.github/workflows/windows-performance.yml +11 -7
  14. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/.gitignore +3 -0
  15. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/.pre-commit-config.yaml +12 -0
  16. pytest_selective-0.4.2/.release-please-manifest.json +3 -0
  17. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/AGENTS.md +5 -0
  18. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/CHANGELOG.md +14 -0
  19. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/CONTRIBUTING.md +69 -1
  20. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/PKG-INFO +11 -1
  21. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/README.md +10 -0
  22. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/action.yml +7 -2
  23. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/actions/plan-lanes/action.yml +3 -3
  24. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/actions/test-impact/action.yml +10 -6
  25. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/ci-toolkit.md +3 -2
  26. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/ci.md +14 -0
  27. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/configuration.md +11 -0
  28. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/local-development.md +5 -0
  29. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/tutorials/adoption-checklist.md +1 -1
  30. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/tutorials/skip-unrelated-ci.md +5 -4
  31. pytest_selective-0.4.2/docs/work-in-progress/tickets/ratchet-duplicates-zero-test-plan.md +18 -0
  32. pytest_selective-0.4.2/docs/work-in-progress/tickets/rerun-selection-test-plan.md +7 -0
  33. pytest_selective-0.4.2/documentation/requirements.in +2 -0
  34. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/documentation/requirements.txt +5 -5
  35. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/pyproject.toml +1 -1
  36. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/skills/pytest-selective/SKILL.md +2 -1
  37. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/skills/pytest-selective/references/adoption.md +2 -2
  38. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/ast_imports.py +26 -2
  39. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/checkpoint.py +20 -9
  40. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/ci.py +7 -3
  41. pytest_selective-0.4.2/src/pytest_selective/git_operations.py +49 -0
  42. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/hygiene.py +3 -10
  43. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/impact_audit_plugin.py +1 -6
  44. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/impact_cli.py +15 -4
  45. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/impact_explanation.py +1 -0
  46. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/impact_first_party.py +4 -11
  47. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/impact_graph.py +18 -43
  48. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/impact_models.py +3 -0
  49. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/impact_name_scope.py +1 -5
  50. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/pytest_expansion.py +21 -70
  51. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/typecheck_scope.py +4 -7
  52. pytest_selective-0.4.2/src/pytest_selective/working_tree.py +41 -0
  53. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/test_checkpoint_policy.py +38 -4
  54. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/test_hygiene.py +8 -0
  55. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/testing/test_portability.py +3 -0
  56. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/testing/test_typecheck_scope.py +20 -0
  57. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/testing/test_working_tree.py +78 -0
  58. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/uv.lock +155 -151
  59. pytest_selective-0.4.0/.release-please-manifest.json +0 -3
  60. pytest_selective-0.4.0/documentation/requirements.in +0 -2
  61. pytest_selective-0.4.0/src/pytest_selective/working_tree.py +0 -43
  62. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/.gitattributes +0 -0
  63. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  64. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  65. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/.github/dependabot.yml +0 -0
  66. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/.github/test-lanes.json +0 -0
  67. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/.github/workflows/observe-ci.yml +0 -0
  68. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/.readthedocs.yaml +0 -0
  69. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/CLAUDE.md +0 -0
  70. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/LICENSE +0 -0
  71. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/PROVENANCE.md +0 -0
  72. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/RELEASING.md +0 -0
  73. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/actions/README.md +0 -0
  74. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/actions/pytest-expansion/action.yml +0 -0
  75. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/agentsync.toml +0 -0
  76. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/README.md +0 -0
  77. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/actions.md +0 -0
  78. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/agents.md +0 -0
  79. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/assets/banner.png +0 -0
  80. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/assets/banner.svg +0 -0
  81. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/assets/brand.json +0 -0
  82. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/assets/icon.png +0 -0
  83. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/assets/logo.svg +0 -0
  84. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/assets/raster-sources.json +0 -0
  85. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/assets/social-preview.png +0 -0
  86. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/assets/social-preview.svg +0 -0
  87. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-22-due-ci.json +0 -0
  88. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-22-due-docs-ci.json +0 -0
  89. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-22-due-docs-widget.json +0 -0
  90. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-22-due-widget.json +0 -0
  91. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-22-input-replays.json +0 -0
  92. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-22-obligation-docs.json +0 -0
  93. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-22-obligation.json +0 -0
  94. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-22-routing.md +0 -0
  95. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-22-selective-docs.json +0 -0
  96. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-22.md +0 -0
  97. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-23-windows-saleor/obligation-baseline.json +0 -0
  98. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-23-windows-saleor/selective-after-3.11.xml +0 -0
  99. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-23-windows-saleor/selective-after-3.14.xml +0 -0
  100. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-23-windows-saleor/selective-after.json +0 -0
  101. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-23-windows-saleor/selective-control-3.11.xml +0 -0
  102. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-23-windows-saleor/selective-control-3.14.xml +0 -0
  103. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-23-windows-saleor/selective-control.json +0 -0
  104. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-23-windows-saleor/selective-earlier.json +0 -0
  105. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/2026-10-23-windows-saleor.md +0 -0
  106. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/benchmarks/windows-runtime.md +0 -0
  107. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/comparison-pytest-impacted.md +0 -0
  108. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/evidence.md +0 -0
  109. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/pytest-hygiene.md +0 -0
  110. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/selection-health.md +0 -0
  111. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/tutorial.md +0 -0
  112. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/tutorials/sidecar-dependencies.md +0 -0
  113. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/work-in-progress/tickets/actions-directory-test-plan.md +0 -0
  114. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/work-in-progress/tickets/codex-selector-correctness-performance-test-plan.md +0 -0
  115. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/work-in-progress/tickets/codex-windows-git-performance-test-plan.md +0 -0
  116. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/work-in-progress/tickets/impacted-improvements-test-plan.md +0 -0
  117. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/work-in-progress/tickets/move-action-implementations-test-plan.md +0 -0
  118. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/docs/work-in-progress/tickets/public-action-onboarding-test-plan.md +0 -0
  119. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/documentation/assets/routing-comparison.svg +0 -0
  120. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/documentation/check_site.py +0 -0
  121. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/documentation/ecosystem.md +0 -0
  122. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/documentation/hooks.py +0 -0
  123. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/documentation/index.md +0 -0
  124. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/documentation/overrides/main.html +0 -0
  125. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/documentation/publishing.md +0 -0
  126. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/mkdocs.yml +0 -0
  127. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/release-please-config.json +0 -0
  128. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/scripts/benchmark_graph.py +0 -0
  129. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/scripts/benchmark_parsing.py +0 -0
  130. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/scripts/benchmark_rust.py +0 -0
  131. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/scripts/check_docs.py +0 -0
  132. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/scripts/check_metadata.py +0 -0
  133. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/scripts/ci_file_config.py +0 -0
  134. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/scripts/compare_impacted.py +0 -0
  135. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/scripts/measure_ci_runs.py +0 -0
  136. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/scripts/public_actions.py +0 -0
  137. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/scripts/render_brand.py +0 -0
  138. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/scripts/run_ci_tests.py +0 -0
  139. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/scripts/smoke_distribution.py +0 -0
  140. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/scripts/tutorial.py +0 -0
  141. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/skills/pytest-selective/agents/openai.yaml +0 -0
  142. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/__init__.py +0 -0
  143. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/__main__.py +0 -0
  144. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/impact_audit.py +0 -0
  145. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/impact_declarations.py +0 -0
  146. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/impact_selection.py +0 -0
  147. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/input_policy.py +0 -0
  148. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/local.py +0 -0
  149. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/parse_batch.py +0 -0
  150. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/plugin.py +0 -0
  151. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/py.typed +0 -0
  152. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/pytest_plugins.py +0 -0
  153. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/pytest_sharding.py +0 -0
  154. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/src/pytest_selective/test_inventory.py +0 -0
  155. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/__init__.py +0 -0
  156. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/test_ci_admission.py +0 -0
  157. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/test_ci_measurements.py +0 -0
  158. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/test_ci_test_execution.py +0 -0
  159. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/test_distribution_metadata.py +0 -0
  160. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/test_public_actions.py +0 -0
  161. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/test_pytest_expansion.py +0 -0
  162. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/test_repository_ci.py +0 -0
  163. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/test_repository_hygiene.py +0 -0
  164. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/test_repository_hygiene.py.gv-dependency.yaml +0 -0
  165. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/test_tutorial.py +0 -0
  166. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/testing/__init__.py +0 -0
  167. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/testing/git_repo.py +0 -0
  168. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/testing/test_ast_imports.py +0 -0
  169. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/testing/test_ci.py +0 -0
  170. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/testing/test_git_repo.py +0 -0
  171. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/testing/test_global_inputs.py +0 -0
  172. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/testing/test_impact_audit.py +0 -0
  173. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/testing/test_impact_explanation.py +0 -0
  174. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/testing/test_impact_first_party.py +0 -0
  175. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/testing/test_impact_graph.py +0 -0
  176. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/testing/test_impact_name_scope.py +0 -0
  177. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/testing/test_parse_batch.py +0 -0
  178. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/testing/test_pytest_plugin.py +0 -0
  179. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/testing/test_pytest_plugins.py +0 -0
  180. {pytest_selective-0.4.0 → pytest_selective-0.4.2}/tests/testing/test_pytest_sharding.py +0 -0
@@ -0,0 +1,80 @@
1
+ {
2
+ "schema": 1,
3
+ "contract": "fbf56a4a852e47539b956057d11e9f3ee56a986bbb3b26ac209dc491d4b45f6e",
4
+ "started_at": "fee809549a83ddfb432cb6599ea57ae73efbe66a",
5
+ "checked_at": "031cafd1748babfecff2ef2629a0320715d8fe9c",
6
+ "entries": [
7
+ {
8
+ "hashes": {
9
+ "src/pytest_selective/hygiene.py::discover_test_modules": "49e17f87bfe24aeb",
10
+ "src/pytest_selective/test_inventory.py::discover_test_files": "567e0cacf263d340"
11
+ },
12
+ "context_hash": "",
13
+ "state": "cleaned"
14
+ },
15
+ {
16
+ "hashes": {
17
+ "src/pytest_selective/impact_graph.py::within": "5417d89a2cedc5ea",
18
+ "src/pytest_selective/typecheck_scope.py::_under": "221f42a33c38887c"
19
+ },
20
+ "context_hash": "",
21
+ "state": "cleaned"
22
+ },
23
+ {
24
+ "hashes": {
25
+ "src/pytest_selective/impact_audit_plugin.py::_git": "be715e103f83b281",
26
+ "src/pytest_selective/impact_graph.py::git": "a53bf9a83105b354"
27
+ },
28
+ "context_hash": "",
29
+ "state": "cleaned"
30
+ },
31
+ {
32
+ "hashes": {
33
+ "src/pytest_selective/impact_audit_plugin.py::_git": "be715e103f83b281",
34
+ "src/pytest_selective/pytest_expansion.py::git": "42cac77499ad42b1"
35
+ },
36
+ "context_hash": "",
37
+ "state": "cleaned"
38
+ },
39
+ {
40
+ "hashes": {
41
+ "src/pytest_selective/impact_first_party.py::_module_name": "c214530cc8698c18",
42
+ "src/pytest_selective/pytest_expansion.py::SourceTree.module_name": "633b2e80dfaac9e6"
43
+ },
44
+ "context_hash": "",
45
+ "state": "cleaned"
46
+ },
47
+ {
48
+ "hashes": {
49
+ "src/pytest_selective/impact_graph.py::git": "a53bf9a83105b354",
50
+ "src/pytest_selective/pytest_expansion.py::git": "42cac77499ad42b1"
51
+ },
52
+ "context_hash": "",
53
+ "state": "cleaned"
54
+ },
55
+ {
56
+ "hashes": {
57
+ "src/pytest_selective/impact_graph.py::git_environment": "1b32a738ac740022",
58
+ "src/pytest_selective/pytest_expansion.py::git_environment": "df7139fadaa213a9"
59
+ },
60
+ "context_hash": "",
61
+ "state": "cleaned"
62
+ },
63
+ {
64
+ "hashes": {
65
+ "src/pytest_selective/impact_name_scope.py::_casefold": "9ecabff1de943de2",
66
+ "src/pytest_selective/impact_graph.py::ModuleIndex.resolve": "a1089dfe99f751cd"
67
+ },
68
+ "context_hash": "",
69
+ "state": "cleaned"
70
+ },
71
+ {
72
+ "hashes": {
73
+ "src/pytest_selective/impact_name_scope.py::_casefold": "9ecabff1de943de2",
74
+ "src/pytest_selective/impact_graph.py::ModuleIndex.__init__": "84c3976fe798fb7e"
75
+ },
76
+ "context_hash": "",
77
+ "state": "cleaned"
78
+ }
79
+ ]
80
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "label": "dry-lint",
4
+ "color": "7E4FC9",
5
+ "labelColor": "24292F",
6
+ "message": "9 cleaned \u00b7 0 remaining"
7
+ }
@@ -0,0 +1,6 @@
1
+ # Uses dry-lint's default tracked-file scope; review include/exclude before changing it.
2
+ # Strict CI refuses incomplete judging; sweep can use a larger one-off budget.
3
+ preset = "sonnet"
4
+
5
+ [budget]
6
+ usd = 0.25
@@ -20,7 +20,7 @@ jobs:
20
20
  mode: ${{ steps.files.outputs.mode }}
21
21
  files: ${{ steps.files.outputs.test_roots_json }}
22
22
  steps:
23
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23
+ - uses: actions/checkout@v7
24
24
  with:
25
25
  fetch-depth: 0
26
26
  - id: plan
@@ -41,11 +41,14 @@ jobs:
41
41
  quality:
42
42
  runs-on: ubuntu-latest
43
43
  steps:
44
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
44
+ - uses: actions/checkout@v7
45
45
  - uses: gigaverse-app/ci-esta/actions/setup-cached-uv@v1
46
46
  with:
47
47
  python-version: '3.14'
48
+ # Semantic judging owns its credentials and fork boundary in the required dry-lint job.
48
49
  - run: uv run --locked pre-commit run --all-files
50
+ env:
51
+ SKIP: dry-lint
49
52
  - name: Check aggregate gate against the execution lanes
50
53
  run: >-
51
54
  uv run --no-project --python 3.14
@@ -65,7 +68,7 @@ jobs:
65
68
  os: [ubuntu-latest, windows-latest, macos-latest]
66
69
  runs-on: ${{ matrix.os }}
67
70
  steps:
68
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
71
+ - uses: actions/checkout@v7
69
72
  - uses: gigaverse-app/ci-esta/actions/setup-cached-uv@v1
70
73
  with:
71
74
  python-version: ${{ matrix.python }}
@@ -90,7 +93,7 @@ jobs:
90
93
  junit-pattern: test-timings.xml
91
94
  artifact-name: test-timings-${{ matrix.python }}-${{ matrix.os }}
92
95
  - run: uv run --locked python scripts/tutorial.py --output tutorial-report.json
93
- - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
96
+ - uses: actions/upload-artifact@v7
94
97
  with:
95
98
  name: evidence-${{ matrix.os }}-${{ matrix.python }}
96
99
  path: |
@@ -103,7 +106,7 @@ jobs:
103
106
  fromJSON(needs.select-tests.outputs.lanes).tests) }}
104
107
  runs-on: ubuntu-latest
105
108
  steps:
106
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
109
+ - uses: actions/checkout@v7
107
110
  - uses: gigaverse-app/ci-esta/actions/setup-cached-uv@v1
108
111
  with:
109
112
  python-version: '3.11'
@@ -120,7 +123,7 @@ jobs:
120
123
  if: ${{ !cancelled() }}
121
124
  runs-on: ubuntu-latest
122
125
  steps:
123
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
126
+ - uses: actions/checkout@v7
124
127
  - uses: gigaverse-app/ci-esta/actions/setup-cached-uv@v1
125
128
  with:
126
129
  python-version: '3.14'
@@ -132,7 +135,7 @@ jobs:
132
135
  ${{ needs.select-tests.result != 'success' ||
133
136
  fromJSON(needs.select-tests.outputs.lanes).tests }}
134
137
  run: uv run --locked python scripts/smoke_distribution.py
135
- - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
138
+ - uses: actions/upload-artifact@v7
136
139
  with:
137
140
  name: distributions
138
141
  path: dist/*
@@ -13,8 +13,8 @@ jobs:
13
13
  docs:
14
14
  runs-on: ubuntu-latest
15
15
  steps:
16
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
17
- - uses: actions/setup-python@v6
16
+ - uses: actions/checkout@v7
17
+ - uses: actions/setup-python@v7
18
18
  with:
19
19
  python-version: '3.13'
20
20
  cache: pip
@@ -23,7 +23,7 @@ jobs:
23
23
  - run: python scripts/render_brand.py --check
24
24
  - run: python -m mkdocs build --strict
25
25
  - run: python documentation/check_site.py
26
- - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
26
+ - uses: actions/upload-artifact@v7
27
27
  with:
28
28
  name: documentation-site
29
29
  path: site/
@@ -0,0 +1,91 @@
1
+ name: dry-lint audit
2
+ # Audits the whole repository against the accepted backlog, then ratchets it: pairs that were refactored away or
3
+ # judged separate are recorded as cleaned in .dry-lint/baseline.json, progress.json and the README block, through a
4
+ # pull request that runs the ordinary checks. The baseline only ever shrinks; a new blocking pair fails the audit
5
+ # and no ratchet pull request is opened. Run it by hand after merging a cleanup to record the progress at once.
6
+ on:
7
+ schedule:
8
+ - cron: '23 6 * * 1'
9
+ workflow_dispatch:
10
+ workflow_call:
11
+ permissions:
12
+ contents: read
13
+ concurrency:
14
+ group: dry-lint-audit
15
+ cancel-in-progress: false
16
+ jobs:
17
+ audit:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v7
21
+ with:
22
+ persist-credentials: false
23
+ - uses: gigaverse-app/dry-lint@v1
24
+ with:
25
+ mode: sweep
26
+ base: HEAD
27
+ python-version: '3.13'
28
+ anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
29
+ plugins: anthropic==2.4.0
30
+ baseline: .dry-lint/baseline.json
31
+ require-complete: 'true'
32
+ budget-usd: '5'
33
+ - name: Retain the audit for human or agent review
34
+ if: always()
35
+ uses: actions/upload-artifact@v7
36
+ with:
37
+ name: dry-lint-audit
38
+ path: |
39
+ ${{ runner.temp }}/dry-lint/report.json
40
+ ${{ runner.temp }}/dry-lint/comment.md
41
+ retention-days: 30
42
+ if-no-files-found: warn
43
+ - name: Require ratchet PR authentication
44
+ env:
45
+ HAS_RATCHET_TOKEN: ${{ secrets.GV_CI_CROSS_REPO_TOKEN != '' }}
46
+ run: |
47
+ if [[ "$HAS_RATCHET_TOKEN" != "true" ]]; then
48
+ echo "::error::Configure the adopter-owned GV_CI_CROSS_REPO_TOKEN with contents and pull-request write access."
49
+ exit 1
50
+ fi
51
+ # The Action's sweep mode only audits. The CLI records the ratchet, reusing the verdicts the audit just stored,
52
+ # and the exact CLI environment installed by the Action. No second install or judge-version drift.
53
+ - name: Record cleaned pairs
54
+ env:
55
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
56
+ run: |
57
+ dry-lint sweep --update --store "$RUNNER_TEMP/dry-lint/verdicts" --budget-usd 5 \
58
+ --readme README.md --repository "$GITHUB_REPOSITORY" --branch main
59
+ # One standing branch: a later run updates the open ratchet PR instead of stacking another. The adopter-owned
60
+ # credential makes the PR run ordinary CI, including the required dry-lint check that refuses any widening.
61
+ - uses: peter-evans/create-pull-request@v8
62
+ with:
63
+ token: ${{ secrets.GV_CI_CROSS_REPO_TOKEN }}
64
+ branch: dry-lint/ratchet
65
+ delete-branch: true
66
+ add-paths: |
67
+ .dry-lint/baseline.json
68
+ .dry-lint/progress.json
69
+ README.md
70
+ commit-message: 'chore(dry-lint): record cleaned duplicate pairs in the baseline'
71
+ title: 'chore(dry-lint): record cleaned duplicate pairs in the baseline'
72
+ body: |
73
+ ## TLDR
74
+
75
+ The weekly dry-lint audit found accepted duplicate pairs that no longer exist or were judged separate,
76
+ and shrinks `.dry-lint/baseline.json`, the progress badge and the README block to match.
77
+
78
+ The baseline can only shrink: the required `dry-lint` check rejects any pull request that widens it.
79
+ The full audit is in the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).
80
+
81
+ ```text
82
+ BEFORE: merged cleanup -> accepted baseline and README still show old debt
83
+ AFTER: complete audit -> reviewed metadata PR -> baseline and README record cleanup
84
+ ```
85
+
86
+ | Policy | Before | After |
87
+ | --- | --- | --- |
88
+ | Accepted pairs | Previously recorded states | Removed/separate pairs marked cleaned; explicit exclusions dismissed |
89
+ | New debt | Rejected | Still rejected; no added allowances |
90
+
91
+ Pair-count changes appear in the baseline/progress diff and the linked audit. No application code changes.
@@ -0,0 +1,30 @@
1
+ name: dry-lint judge
2
+ on:
3
+ workflow_run:
4
+ workflows: [dry-lint]
5
+ types: [completed]
6
+ permissions:
7
+ actions: read
8
+ checks: write
9
+ contents: read
10
+ pull-requests: write
11
+ concurrency:
12
+ group: dry-lint-judge-${{ github.event.workflow_run.head_branch }}
13
+ cancel-in-progress: true
14
+ jobs:
15
+ judge:
16
+ if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.head_repository.full_name != github.repository
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ # Only trusted default-branch settings and baseline; fork code is artifact data.
20
+ - uses: actions/checkout@v7
21
+ with:
22
+ persist-credentials: false
23
+ - uses: gigaverse-app/dry-lint@v1
24
+ with:
25
+ anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
26
+ plugins: anthropic==2.4.0
27
+ baseline: .dry-lint/baseline.json
28
+ require-complete: 'true'
29
+ comment: 'true'
30
+ comment-on-clean: 'true'
@@ -0,0 +1,41 @@
1
+ name: dry-lint
2
+ on:
3
+ pull_request:
4
+ types: [opened, synchronize, reopened]
5
+ push:
6
+ branches: [main]
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+ pull-requests: write
12
+
13
+ concurrency:
14
+ group: dry-lint-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }}
15
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
16
+
17
+ jobs:
18
+ check:
19
+ # A detection-only fork job must never satisfy the required judged check.
20
+ name: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && 'dry-lint detect' || 'dry-lint' }}
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v7
24
+ with:
25
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
26
+ fetch-depth: 1
27
+ persist-credentials: false
28
+ - uses: gigaverse-app/dry-lint@v1
29
+ with:
30
+ anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
31
+ plugins: anthropic==2.4.0
32
+ baseline: .dry-lint/baseline.json
33
+ require-complete: 'true'
34
+ comment: 'true'
35
+ comment-on-clean: 'true'
36
+
37
+ audit:
38
+ # Manual full validation also exercises the scheduled ratchet before its first merge.
39
+ if: github.event_name == 'workflow_dispatch'
40
+ uses: ./.github/workflows/dry-lint-audit.yml
41
+ secrets: inherit
@@ -14,7 +14,7 @@ jobs:
14
14
  timeout-minutes: 5
15
15
  steps:
16
16
  # Only reads PR metadata. Never check out or execute PR code in this workflow.
17
- - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
17
+ - uses: amannn/action-semantic-pull-request@v6
18
18
  with:
19
19
  types: |
20
20
  feat
@@ -27,7 +27,7 @@ jobs:
27
27
  fromJSON(needs.select-tests.outputs.lanes).tests) }}
28
28
  runs-on: ubuntu-latest
29
29
  steps:
30
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
30
+ - uses: actions/checkout@v7
31
31
  with:
32
32
  fetch-depth: 0
33
33
  - name: Use the reviewed repository graph
@@ -28,7 +28,7 @@ jobs:
28
28
  steps:
29
29
  - name: Only process the tested main head
30
30
  id: current
31
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
31
+ uses: actions/github-script@v9
32
32
  with:
33
33
  script: |
34
34
  const {data: branch} = await github.rest.repos.getBranch({...context.repo, branch: 'main'});
@@ -44,7 +44,7 @@ jobs:
44
44
  fi
45
45
  - name: Prepare release PR or publish its GitHub release
46
46
  if: steps.current.outputs.matches == 'true'
47
- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
47
+ uses: googleapis/release-please-action@v5
48
48
  with:
49
49
  # This credential triggers CI on release PRs and Publish on releases.
50
50
  # GITHUB_TOKEN would suppress those follow-up workflow events.
@@ -10,8 +10,8 @@ jobs:
10
10
  validate-tag:
11
11
  runs-on: ubuntu-latest
12
12
  steps:
13
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
14
- - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
13
+ - uses: actions/checkout@v7
14
+ - uses: astral-sh/setup-uv@v10.3.3
15
15
  with:
16
16
  python-version: '3.14'
17
17
  - name: Require a version-matching tag
@@ -31,12 +31,12 @@ jobs:
31
31
  permissions:
32
32
  id-token: write
33
33
  steps:
34
- - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
34
+ - uses: actions/download-artifact@v8
35
35
  with:
36
36
  name: distributions
37
37
  path: dist
38
38
  - name: Publish tested distributions with attestations
39
- uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # release/v1
39
+ uses: pypa/gh-action-pypi-publish@release/v1
40
40
 
41
41
  action-version:
42
42
  needs: publish
@@ -21,34 +21,38 @@ jobs:
21
21
  matrix:
22
22
  python: ['3.11', '3.14']
23
23
  steps:
24
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
24
+ - uses: actions/checkout@v7
25
25
  with:
26
26
  ref: ${{ inputs.baseline || github.event.pull_request.base.sha }}
27
27
  path: baseline
28
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
28
+ - uses: actions/checkout@v7
29
29
  with:
30
30
  ref: ${{ github.event.pull_request.head.sha || github.sha }}
31
31
  path: candidate
32
- - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e
32
+ - uses: astral-sh/setup-uv@v10.3.3
33
33
  with:
34
34
  python-version: ${{ matrix.python }}
35
35
  cache-suffix: matched-windows-${{ matrix.python }}
36
36
  - run: uv sync --directory baseline --locked
37
37
  - run: uv sync --directory candidate --locked
38
+ - name: Preserve the baseline Git search path
39
+ shell: pwsh
40
+ run: '"BASELINE_GIT_PATH=$env:PATH" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append'
41
+ - uses: gigaverse-app/ci-esta/actions/native-windows-git@v1
38
42
  - name: Run complete suites in both orders on the same worker
39
43
  shell: pwsh
40
44
  run: |
41
45
  $results = Join-Path $env:GITHUB_WORKSPACE 'measurements'
42
46
  New-Item -ItemType Directory -Path $results | Out-Null
43
47
  $measurements = @()
44
- $originalPath = $env:PATH
48
+ $originalPath = $env:BASELINE_GIT_PATH
49
+ $nativeGitPath = $env:PATH
45
50
  foreach ($sample in @('baseline-1', 'candidate-1', 'candidate-2', 'baseline-2')) {
46
51
  $arm = $sample.Split('-')[0]
47
52
  Push-Location (Join-Path $env:GITHUB_WORKSPACE $arm)
48
53
  try {
49
54
  # Start every arm with the same PATH; only the candidate opts in.
50
- $env:PATH = $originalPath
51
- if ($arm -eq 'candidate') { ./scripts/use_native_windows_git.ps1 }
55
+ $env:PATH = if ($arm -eq 'candidate') { $nativeGitPath } else { $originalPath }
52
56
  $sha = git rev-parse HEAD
53
57
  # Keep repeated arms from timing cleanup of an earlier arm's Git fixtures.
54
58
  $temp = Join-Path $env:RUNNER_TEMP "matched-pytest-$sample"
@@ -63,7 +67,7 @@ jobs:
63
67
  if ($result -ne 0) { throw "${sample} failed: $result" }
64
68
  } finally { Pop-Location }
65
69
  }
66
- - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
70
+ - uses: actions/upload-artifact@v7
67
71
  if: always()
68
72
  with:
69
73
  name: matched-windows-${{ matrix.python }}
@@ -15,3 +15,6 @@ tutorial-report.json
15
15
  # Built documentation
16
16
  /site/
17
17
  /.venv-docs/
18
+
19
+ # Local judge credentials
20
+ .env
@@ -1,3 +1,6 @@
1
+ minimum_pre_commit_version: '4.3.0'
2
+ default_install_hook_types: [pre-commit, prepare-commit-msg]
3
+ default_stages: [pre-commit]
1
4
  repos:
2
5
  - repo: local
3
6
  hooks:
@@ -17,3 +20,12 @@ repos:
17
20
  language: system
18
21
  pass_filenames: false
19
22
  types: [markdown]
23
+
24
+ - repo: https://github.com/gigaverse-app/dry-lint
25
+ rev: v1
26
+ hooks:
27
+ - id: dry-lint
28
+ args: [--preset, sonnet, --baseline, .dry-lint/baseline.json]
29
+ language_version: python3.13
30
+ - id: dry-lint-trailers
31
+ language_version: python3.13
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.4.2"
3
+ }
@@ -67,3 +67,8 @@ workflows, selector configuration, helpers, locks and non-import inputs; missing
67
67
  history or failed planning must execute the full affected lanes and fail the
68
68
  required planning gate. Ordinary PRs and pushes use selection; manual/release/scheduled/full overrides
69
69
  remain complete checkpoints.
70
+
71
+ ## GitHub Actions versions
72
+
73
+ - ALWAYS use published major-version tags for established GitHub Actions, for example `actions/checkout@v7` and `actions/upload-artifact@v7`, so compatible fixes arrive without per-commit maintenance. NEVER replace those tags with commit hashes unless the user explicitly requests that exact exception. If a publisher has no major-version tag, use its published release tag rather than inventing a ref or substituting a hash.
74
+ - ALWAYS use `gigaverse-app/dry-lint@main` until its v1 launch, then use `@v1`. Apply the same ref in workflow examples and adoption instructions.
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.2](https://github.com/gigaverse-app/pytest-selective/compare/v0.4.1...v0.4.2) (2026-11-07)
4
+
5
+
6
+ ### Bug fixes
7
+
8
+ * consolidate duplicated selection helpers and retire baseline debt ([#77](https://github.com/gigaverse-app/pytest-selective/issues/77)) ([a7427bf](https://github.com/gigaverse-app/pytest-selective/commit/a7427bf6f550789a835a38f3326b8a971bef114f))
9
+
10
+ ## [0.4.1](https://github.com/gigaverse-app/pytest-selective/compare/v0.4.0...v0.4.1) (2026-10-24)
11
+
12
+
13
+ ### Bug fixes
14
+
15
+ * **selection:** preserve rerun scope and record planner reasons ([29d1df8](https://github.com/gigaverse-app/pytest-selective/commit/29d1df806816203bad1e7a4a53f38b7e75c1d268))
16
+
3
17
  ## [0.4.0](https://github.com/gigaverse-app/pytest-selective/compare/v0.3.0...v0.4.0) (2026-10-24)
4
18
 
5
19
 
@@ -32,6 +32,23 @@ Changes to collection policy, runtime auditing, or the composite action need tes
32
32
  at that boundary. Update the tutorial when the public command contract changes.
33
33
  Keep performance claims tied to reproducible measurements and their limitations.
34
34
 
35
+ ### Shared implementation owners
36
+
37
+ | Contract | Owner |
38
+ | --- | --- |
39
+ | Repository-scoped Git, hook environment isolation, binary and UTF-8 output | `git_operations.py` |
40
+ | Private working-tree snapshots, staged inputs and index timestamp preservation | `working_tree.py` |
41
+ | Python module names, Unicode matching and dotted import relationships | `ast_imports.py` |
42
+ | Test-file discovery and cache exclusions | `test_inventory.py` |
43
+ | Repository-relative path containment, including the dot root | `impact_graph.within` |
44
+
45
+ The expansion guard imports the first three owners directly when run as a script;
46
+ keep them stdlib-only. Its snapshot updates Python paths and preserves the stat
47
+ cache; the planner rebuilds the staged entries to see edits hidden by index flags
48
+ and updates all inputs. Preserve the copied index timestamp: Git uses it to detect
49
+ same-size edits made within its stat-cache window. The snapshot tests cover ordinary indexes,
50
+ split indexes and linked worktrees without modifying the user's index or HEAD.
51
+
35
52
  ## CI and dependency maintenance
36
53
 
37
54
  CI tests Python 3.11 and 3.14 on Linux, Windows, and macOS. A separate job checks
@@ -41,7 +58,8 @@ environment on both Python versions. Evidence artifacts carry coverage and tutor
41
58
 
42
59
  Use `uv lock --upgrade-package NAME` for a targeted dependency update and run the
43
60
  affected checks. Dependabot proposes weekly uv and GitHub Actions updates.
44
- Actions are pinned to commits. Review their release notes before updating pins.
61
+ Established Actions use published major-version tags; use a published release tag when no major tag exists.
62
+ Review release notes when upgrading a major version.
45
63
 
46
64
  ## Pull requests
47
65
 
@@ -152,3 +170,53 @@ workflows, selector configuration, helpers, locks and non-import inputs; missing
152
170
  history or failed planning must execute the full affected lanes and fail the
153
171
  required planning gate. Ordinary PRs and pushes use selection; manual/release/scheduled/full overrides
154
172
  remain complete checkpoints.
173
+
174
+ ## Duplication
175
+
176
+ `uv run pre-commit install` installs both the staged dry-lint check and the
177
+ `prepare-commit-msg` trailer hook, alongside Ruff and documentation checks.
178
+ Put your own `ANTHROPIC_API_KEY` in the shell or the ignored `.env`. The hook uses
179
+ Sonnet, Python 3.13 and the same accepted baseline as CI. Missing judge credentials
180
+ fail strict checking. The trailer records duplicates fixed before the first push,
181
+ so the PR sticky includes that local prevention credit.
182
+
183
+ The required judged `dry-lint` check rejects new blocking duplicates, incomplete
184
+ judgments and baseline widening. Forks detect candidates without secrets; the
185
+ trusted judge workflow assesses their artifacts. CI's general quality job skips
186
+ only the semantic hook, because the dedicated required job owns judging and its
187
+ credentials. Other pre-commit hooks still run there.
188
+
189
+ When a finding appears, inspect both functions. Reuse the existing owner when they
190
+ implement one concept, preserve selection/fallback behavior and run the affected
191
+ tests. For genuinely distinct concepts, add a narrow `distinct-from` comment and
192
+ its reason. Do not weaken thresholds or use blanket ignores to make CI pass.
193
+
194
+ The PR sticky tracks findings and fixes across pushes. Every default-branch push
195
+ can finish folding merged PRs into the repository ledger. The README sign shows
196
+ new duplicates prevented separately from old debt cleaned and remaining. Counts
197
+ are pairs, not lines or estimated time savings; dismissals never count as cleanup.
198
+ The cache-backed ledger starts a new accounting period if evicted.
199
+
200
+ The weekly/manual **dry-lint audit** retains its reports, then opens or updates one
201
+ `dry-lint/ratchet` PR with only the baseline, progress JSON and README metadata.
202
+ The inherited `GV_CI_CROSS_REPO_TOKEN` lets that PR run ordinary CI. New debt,
203
+ incomplete judging or missing authentication fails visibly. Humans and agents
204
+ choose refactors; the audit never edits application code or merges its own PR.
205
+
206
+ ```bash
207
+ uv tool install --no-config --python 3.13 'dry-lint[anthropic] @ git+https://github.com/gigaverse-app/dry-lint@v1'
208
+ dry-lint sweep --budget-usd 5 --report audit.json --output audit.md
209
+ # Review both sides, refactor and test before recording progress.
210
+ dry-lint sweep --update --budget-usd 5 --readme README.md --repository gigaverse-app/pytest-selective --repository-id 1430133155
211
+ ```
212
+
213
+ Commit the generated metadata with a cleanup, or dispatch the audit after the
214
+ cleanup merges. A manual run of the **dry-lint** workflow also calls the same
215
+ reusable audit, allowing validation on an adoption branch before the new scheduled
216
+ workflow exists on `main`. Ratchet PRs target the selected branch. Preserve the nine-pair adoption history; never delete and
217
+ reinitialize the baseline. No-change audits produce no metadata diff. Keep raw
218
+ reports and verdict caches outside Git. The scope uses dry-lint's default
219
+ exclusions; changes to detection policy require an explicitly reviewed migration.
220
+
221
+ See the [full adoption playbook](https://github.com/gigaverse-app/dry-lint/blob/main/docs/adoption-playbook.md)
222
+ and [sweep semantics](https://github.com/gigaverse-app/dry-lint/blob/main/docs/sweeps.md).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pytest-selective
3
- Version: 0.4.0
3
+ Version: 0.4.2
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/
@@ -316,3 +316,13 @@ show provenance, runner cost and the actual dependency paths. This repository
316
316
  also uses CI-esta's shared native Windows Git action while retaining full
317
317
  coverage on all six Python/platform lanes.
318
318
 
319
+ ### Duplicates kept out
320
+
321
+ Local hooks and the required PR check prevent new copies. The weekly audit
322
+ records progress against the accepted backlog. [Setup and cleanup workflow](CONTRIBUTING.md#duplication).
323
+
324
+ <!-- dry-lint-progress:start -->
325
+ <a href="https://github.com/gigaverse-app/dry-lint"><picture><source media="(prefers-color-scheme: dark)" srcset="https://dry-lint-counter.gigaverse-oss.workers.dev/v1/sign.svg?theme=dark&amp;key=f8e41d50ca4018845c305f2f0dbd1cfd6d9b281e53b352490af53154a54e6265&amp;name=gigaverse-app%2Fpytest-selective&amp;cleaned=9&amp;remaining=0&amp;dismissed=0"><img alt="dry-lint in gigaverse-app/pytest-selective: new duplicates kept out of its pull requests, 9 old duplicates cleaned up, 0 remaining" src="https://dry-lint-counter.gigaverse-oss.workers.dev/v1/sign.svg?key=f8e41d50ca4018845c305f2f0dbd1cfd6d9b281e53b352490af53154a54e6265&amp;name=gigaverse-app%2Fpytest-selective&amp;cleaned=9&amp;remaining=0&amp;dismissed=0" width="100%"></picture></a>
326
+
327
+ [Accepted backlog](.dry-lint/baseline.json) · [Weekly audit](https://github.com/gigaverse-app/pytest-selective/actions/workflows/dry-lint-audit.yml)
328
+ <!-- dry-lint-progress:end -->