ruff-sync 0.1.7.dev2__tar.gz → 0.1.9.dev0__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 (217) hide show
  1. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/DEPENDENCIES.md +2 -0
  2. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/args_refactor_plan.md +14 -8
  3. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/cli_animation_plan.md +1 -3
  4. ruff_sync-0.1.9.dev0/.agents/decisions/0004-httpx2-respx-test-aliasing.md +51 -0
  5. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/decisions/README.md +1 -0
  6. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/formatters-architecture.md +7 -6
  7. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/gitlab-reports.md +26 -16
  8. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/issue-102-context.md +12 -2
  9. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/plans/issue-116-config-validation.md +19 -20
  10. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/dirty-equals/SKILL.md +7 -4
  11. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/dirty-equals/references/common-matchers.md +7 -5
  12. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/dirty-equals/references/toml-matching.md +8 -6
  13. {ruff_sync-0.1.7.dev2/.agents/skills/ruff-sync-usage → ruff_sync-0.1.9.dev0/.agents/skills/ruff-sync}/SKILL.md +1 -1
  14. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/textual/SKILL.md +3 -0
  15. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/textual/references/events.md +3 -0
  16. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/textual/references/testing.md +1 -0
  17. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/textual/references/widgets.md +3 -0
  18. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/type-checking/references/advanced-narrowing.md +4 -0
  19. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/type-checking/references/error-code-lookup.md +10 -6
  20. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/type-checking/references/generics.md +5 -1
  21. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/type-checking/references/naming.md +2 -0
  22. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/type-checking/references/protocol-patterns.md +7 -2
  23. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/type-checking/references/refactoring-patterns.md +4 -0
  24. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/warnings-control/SKILL.md +5 -0
  25. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/tui_design.md +3 -1
  26. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/workflows/update-screenshots.md +1 -1
  27. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.github/workflows/ci.yaml +7 -7
  28. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.github/workflows/complexity.yaml +1 -1
  29. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.pre-commit-config.yaml +1 -1
  30. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/AGENTS.md +9 -7
  31. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/CONTRIBUTING.md +3 -3
  32. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/PKG-INFO +32 -6
  33. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/README.md +28 -1
  34. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/agent-skill.md +10 -6
  35. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/contributing.md +1 -1
  36. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/pyproject.toml +16 -14
  37. ruff_sync-0.1.9.dev0/src/ruff_sync/.agents/skills/ruff-sync/SKILL.md +147 -0
  38. ruff_sync-0.1.9.dev0/src/ruff_sync/.agents/skills/ruff-sync/references/ci-integration.md +233 -0
  39. ruff_sync-0.1.9.dev0/src/ruff_sync/.agents/skills/ruff-sync/references/configuration.md +164 -0
  40. ruff_sync-0.1.9.dev0/src/ruff_sync/.agents/skills/ruff-sync/references/troubleshooting.md +157 -0
  41. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/config_io.py +1 -1
  42. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/constants.py +3 -3
  43. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/core.py +17 -44
  44. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/types_.py +1 -3
  45. ruff_sync-0.1.9.dev0/src/sitecustomize.py +20 -0
  46. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tasks.py +5 -5
  47. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/conftest.py +5 -45
  48. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/ruff.toml +2 -0
  49. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_basic.py +42 -19
  50. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_benchmarks.py +1 -1
  51. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_check.py +60 -49
  52. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_config_validation.py +19 -18
  53. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_deprecation.py +9 -8
  54. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_e2e.py +11 -11
  55. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_project.py +2 -2
  56. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_scaffold.py +5 -5
  57. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_serialization.py +1 -1
  58. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_url_handling.py +9 -5
  59. ruff_sync-0.1.9.dev0/uv.lock +1988 -0
  60. ruff_sync-0.1.7.dev2/uv.lock +0 -2097
  61. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/TESTING.md +0 -0
  62. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/decisions/0001-type-refactoring-strategy.md +0 -0
  63. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/decisions/0002-tui-node-ast.md +0 -0
  64. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/decisions/0003-argument-resolution-layers.md +0 -0
  65. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/doc-fix.md +0 -0
  66. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/docs_update_plan.md +0 -0
  67. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/plans/issue-100-roadmap-plan.md +0 -0
  68. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/ruff.toml +0 -0
  69. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/adr/SKILL.md +0 -0
  70. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/dirty-equals/trigger_eval.json +0 -0
  71. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/gh-issues/SKILL.md +0 -0
  72. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/mike/SKILL.md +0 -0
  73. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/mike/references/commands.md +0 -0
  74. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/mkdocs-generation/SKILL.md +0 -0
  75. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/mkdocs-generation/examples.md +0 -0
  76. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/mkdocs-generation/templates/api-reference.md +0 -0
  77. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/mkdocs-generation/templates/getting-started.md +0 -0
  78. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/mkdocs-generation/templates/index.md +0 -0
  79. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/mkdocs-generation/templates/mkdocs.yml +0 -0
  80. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/release-notes-generation/SKILL.md +0 -0
  81. {ruff_sync-0.1.7.dev2/.agents/skills/ruff-sync-usage → ruff_sync-0.1.9.dev0/.agents/skills/ruff-sync}/references/ci-integration.md +0 -0
  82. {ruff_sync-0.1.7.dev2/.agents/skills/ruff-sync-usage → ruff_sync-0.1.9.dev0/.agents/skills/ruff-sync}/references/configuration.md +0 -0
  83. {ruff_sync-0.1.7.dev2/.agents/skills/ruff-sync-usage → ruff_sync-0.1.9.dev0/.agents/skills/ruff-sync}/references/troubleshooting.md +0 -0
  84. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/skill-creator/SKILL.md +0 -0
  85. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/skill-creator/references/best-practices.md +0 -0
  86. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/skill-creator/references/evaluating-skills.md +0 -0
  87. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/skill-creator/references/optimizing-descriptions.md +0 -0
  88. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/skill-creator/references/quickstart.md +0 -0
  89. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/skill-creator/references/sources.md +0 -0
  90. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/skill-creator/references/using-scripts.md +0 -0
  91. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/skill-creator/scripts/scaffold_skill.py +0 -0
  92. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/textual/examples/basic_app.py +0 -0
  93. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/textual/examples/reactive_example.py +0 -0
  94. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/textual/references/styling.md +0 -0
  95. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/type-checking/SKILL.md +0 -0
  96. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/type-checking/evals.json +0 -0
  97. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/type-checking/references/quickstart.md +0 -0
  98. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/skills/type-checking/scripts/audit_types.py +0 -0
  99. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/tui_requirements.md +0 -0
  100. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/tui_rule_browsing.md +0 -0
  101. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/tui_rule_browsing_design.md +0 -0
  102. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/workflows/add-test-case.md +0 -0
  103. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.agents/workflows/update-recordings.md +0 -0
  104. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.git-blame-ignore-revs +0 -0
  105. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.github/dependabot.yml +0 -0
  106. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.github/workflows/codspeed.yaml +0 -0
  107. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.gitignore +0 -0
  108. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/.pre-commit-hooks.yaml +0 -0
  109. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/LICENSE.md +0 -0
  110. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/codecov.yml +0 -0
  111. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/configs/data-science-engineering/ruff.toml +0 -0
  112. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/configs/fastapi/ruff.toml +0 -0
  113. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/configs/kitchen-sink/ruff.toml +0 -0
  114. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/assets/favicon.png +0 -0
  115. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/assets/github-job-summary.png +0 -0
  116. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/assets/github-pr-annotation.png +0 -0
  117. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/assets/inspect-main.png +0 -0
  118. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/assets/inspect-search.png +0 -0
  119. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/assets/logo.png +0 -0
  120. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/assets/recordings/check_drift.gif +0 -0
  121. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/assets/recordings/check_in_sync.gif +0 -0
  122. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/assets/recordings/help_overview.gif +0 -0
  123. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/assets/recordings/init_project.gif +0 -0
  124. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/assets/recordings/pull_basic.gif +0 -0
  125. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/assets/recordings/validate_strict.gif +0 -0
  126. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/assets/ruff_sync_banner.png +0 -0
  127. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/assets/screenshots/dashboard.svg +0 -0
  128. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/assets/screenshots/legend_help.svg +0 -0
  129. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/assets/screenshots/rule_details.svg +0 -0
  130. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/assets/screenshots/screenshot_sample.toml +0 -0
  131. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/assets/screenshots/search_omnibox.svg +0 -0
  132. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/best-practices.md +0 -0
  133. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/ci-integration.md +0 -0
  134. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/configuration.md +0 -0
  135. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/examples/advanced-config.toml +0 -0
  136. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/examples/basic-config.toml +0 -0
  137. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/gen_ref_pages.py +0 -0
  138. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/index.md +0 -0
  139. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/inspect.md +0 -0
  140. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/installation.md +0 -0
  141. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/overrides/main.html +0 -0
  142. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/overrides/partials/version_warning.html +0 -0
  143. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/pre-commit.md +0 -0
  144. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/pre-defined-configs.md +0 -0
  145. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/troubleshooting.md +0 -0
  146. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/url-resolution.md +0 -0
  147. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/docs/usage.md +0 -0
  148. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/mkdocs.yml +0 -0
  149. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/scripts/check_dogfood.sh +0 -0
  150. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/scripts/generate_tui_screenshots.py +0 -0
  151. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/scripts/gitclone_dogfood.sh +0 -0
  152. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/scripts/pull_dogfood.sh +0 -0
  153. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/skills-lock.json +0 -0
  154. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/__init__.py +0 -0
  155. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/__main__.py +0 -0
  156. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/cli.py +0 -0
  157. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/dependencies.py +0 -0
  158. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/formatters.py +0 -0
  159. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/pre_commit.py +0 -0
  160. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/system.py +0 -0
  161. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/tui/__init__.py +0 -0
  162. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/tui/app.py +0 -0
  163. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/tui/constants.py +0 -0
  164. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/tui/screens.py +0 -0
  165. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/tui/themes.py +0 -0
  166. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/tui/types_.py +0 -0
  167. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/tui/widgets.py +0 -0
  168. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/src/ruff_sync/validation.py +0 -0
  169. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tapes/_common.tape +0 -0
  170. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tapes/check_drift.tape +0 -0
  171. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tapes/check_in_sync.tape +0 -0
  172. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tapes/help_overview.tape +0 -0
  173. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tapes/init_project.tape +0 -0
  174. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tapes/pull_basic.tape +0 -0
  175. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tapes/validate_strict.tape +0 -0
  176. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/__init__.py +0 -0
  177. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/multi_upstream_final.toml +1 -1
  178. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/multi_upstream_initial.toml +0 -0
  179. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/multi_upstream_up1.toml +0 -0
  180. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/multi_upstream_up2.toml +0 -0
  181. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/no_changes_final.toml +0 -0
  182. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/no_changes_initial.toml +0 -0
  183. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/no_changes_upstream.toml +0 -0
  184. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/no_dotted_keys_final.toml +0 -0
  185. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/no_dotted_keys_initial.toml +0 -0
  186. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/no_dotted_keys_upstream.toml +0 -0
  187. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/no_ruff_cfg_final.toml +0 -0
  188. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/no_ruff_cfg_initial.toml +0 -0
  189. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/no_ruff_cfg_upstream.toml +0 -0
  190. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/readme_excludes_final.toml +0 -0
  191. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/readme_excludes_initial.toml +0 -0
  192. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/readme_excludes_upstream.toml +0 -0
  193. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/standard_final.toml +0 -0
  194. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/standard_initial.toml +0 -0
  195. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/lifecycle_tomls/standard_upstream.toml +0 -0
  196. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_ci_integration.py +0 -0
  197. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_ci_validation.py +0 -0
  198. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_config_io.py +0 -0
  199. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_constants.py +0 -0
  200. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_corner_cases.py +0 -0
  201. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_dependencies.py +0 -0
  202. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_formatters.py +0 -0
  203. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_git_fetch.py +0 -0
  204. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_minimal_imports.sh +0 -0
  205. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_pre_commit.py +0 -0
  206. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_rule_logic.py +0 -0
  207. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_system.py +0 -0
  208. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_toml_operations.py +0 -0
  209. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/test_whitespace.py +0 -0
  210. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/tui/__init__.py +0 -0
  211. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/tui/conftest.py +0 -0
  212. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/tui/test_themes.py +0 -0
  213. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/tui/test_tui.py +0 -0
  214. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/tui/test_tui_types.py +0 -0
  215. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/w_ruff_sync_cfg/pyproject.toml +0 -0
  216. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/wo_ruff_cfg/pyproject.toml +0 -0
  217. {ruff_sync-0.1.7.dev2 → ruff_sync-0.1.9.dev0}/tests/wo_ruff_sync_cfg/pyproject.toml +0 -0
@@ -29,10 +29,12 @@ Never import optional dependencies at the top level of a module. All imports mus
29
29
  def run_tui_feature():
30
30
  # 1. First, check availability (fast, lightweight)
31
31
  from ruff_sync.dependencies import require_dependency
32
+
32
33
  require_dependency("textual", extra_name="tui")
33
34
 
34
35
  # 2. Then, perform local import (delayed expensive cycle)
35
36
  from textual.app import App
37
+
36
38
  ...
37
39
  ```
38
40
 
@@ -96,11 +96,11 @@ class ExecutionArgs(NamedTuple):
96
96
  semantic: bool
97
97
  diff: bool
98
98
  init: bool
99
- pre_commit: bool # plain bool — MISSING resolved to default
99
+ pre_commit: bool # plain bool — MISSING resolved to default
100
100
  save: bool | None
101
101
  output_format: OutputFormat
102
- validate: bool # plain bool — MISSING resolved to default
103
- strict: bool # plain bool — MISSING resolved to default
102
+ validate: bool # plain bool — MISSING resolved to default
103
+ strict: bool # plain bool — MISSING resolved to default
104
104
  ```
105
105
 
106
106
  > [!NOTE]
@@ -120,9 +120,9 @@ class Arguments(NamedTuple):
120
120
  def resolve(self) -> ExecutionArgs:
121
121
  """Resolve all MISSING sentinels to their effective defaults for execution."""
122
122
  _, _, _, eff_validate, eff_strict, eff_pre_commit = resolve_defaults(
123
- MISSING, # branch — already resolved, pass MISSING to skip
124
- MISSING, # path — already resolved, pass MISSING to skip
125
- MISSING, # exclude — already resolved, pass MISSING to skip
123
+ MISSING, # branch — already resolved, pass MISSING to skip
124
+ MISSING, # path — already resolved, pass MISSING to skip
125
+ MISSING, # exclude — already resolved, pass MISSING to skip
126
126
  self.validate,
127
127
  self.strict,
128
128
  self.pre_commit,
@@ -205,11 +205,14 @@ Replace the `resolve_defaults()` call in `Arguments.resolve()` with `resolve_boo
205
205
  ```python
206
206
  from ruff_sync.constants import resolve_bool_flags
207
207
 
208
+
208
209
  class Arguments(NamedTuple):
209
210
  # ...
210
211
  def resolve(self) -> ExecutionArgs:
211
212
  eff_validate, eff_strict, eff_pre_commit = resolve_bool_flags(
212
- self.validate, self.strict, self.pre_commit,
213
+ self.validate,
214
+ self.strict,
215
+ self.pre_commit,
213
216
  )
214
217
  return ExecutionArgs(
215
218
  command=self.command,
@@ -291,7 +294,7 @@ call is actually redundant. However, if the function is also used by code that p
291
294
  async def _merge_multiple_upstreams(
292
295
  target_doc: TOMLDocument,
293
296
  is_target_ruff_toml: bool,
294
- args: ExecutionArgs, # ← changed from Arguments
297
+ args: ExecutionArgs, # ← changed from Arguments
295
298
  client: httpx.AsyncClient,
296
299
  ) -> TOMLDocument:
297
300
  # No resolve_defaults() needed — args already has plain values
@@ -363,17 +366,20 @@ Add **new** tests for `resolve_bool_flags()`:
363
366
  ```python
364
367
  from ruff_sync.constants import resolve_bool_flags
365
368
 
369
+
366
370
  def test_resolve_bool_flags_all_missing():
367
371
  validate, strict, pre_commit = resolve_bool_flags(MISSING, MISSING, MISSING)
368
372
  assert validate is False
369
373
  assert strict is False
370
374
  assert pre_commit is True
371
375
 
376
+
372
377
  def test_resolve_bool_flags_strict_implies_validate():
373
378
  validate, strict, pre_commit = resolve_bool_flags(MISSING, True, MISSING)
374
379
  assert validate is True
375
380
  assert strict is True
376
381
 
382
+
377
383
  def test_resolve_bool_flags_explicit_false():
378
384
  validate, strict, pre_commit = resolve_bool_flags(False, False, False)
379
385
  assert validate is False
@@ -441,9 +441,7 @@ def recordings(ctx, tape=None):
441
441
  tape_files = [tape_file]
442
442
  else:
443
443
  # Process all tape files except _common.tape
444
- tape_files = sorted(
445
- f for f in tapes_dir.glob("*.tape") if not f.name.startswith("_")
446
- )
444
+ tape_files = sorted(f for f in tapes_dir.glob("*.tape") if not f.name.startswith("_"))
447
445
 
448
446
  if not tape_files:
449
447
  print("⚠️ No tape files found in tapes/")
@@ -0,0 +1,51 @@
1
+ # ADR 0004: httpx2 Integration, Respx Mocking, and Test-Guarded Process Aliasing
2
+
3
+ ---
4
+ status: accepted
5
+ date: 2026-07-25
6
+ decider: Kilo59 / Agent
7
+ ---
8
+
9
+ ## Context & Motivation for Switching to `httpx2`
10
+
11
+ `ruff-sync` relies on an asynchronous HTTP client to pull upstream Ruff linter configurations from GitHub and GitLab repositories.
12
+
13
+ We migrated from `httpx` to **`httpx2`** based on the official rationale provided by **Pydantic Services**:
14
+ - **Active Stewardship & Security Maintenance**: Due to a significant period of reduced maintenance activity and inactivity on the original `httpx` package, Pydantic Services created `httpx2` as an actively maintained successor to guarantee timely security updates, bug fixes, and continuous maintenance for a critical core library in the Python ecosystem.
15
+ - **Ecosystem Continuity**: The "2" in `httpx2` functions as a versioning marker for this new era of Pydantic Services maintenance rather than a breaking paradigm rewrite, preserving full compatibility while delivering active upstream support.
16
+ - **Strict Typing**: `httpx2` provides first-class typing out of the box, integrating cleanly with our `mypy` strict mode requirements.
17
+
18
+ Previously, HTTP test mocking relied on the third-party `pytest-httpx2` plugin. We needed to clean up our HTTP dependencies, eliminate `pytest-httpx2`, and migrate to standard `respx` (`>=0.23.1`) while ensuring zero side-effects on production execution.
19
+
20
+ ## Decision
21
+
22
+ 1. **Direct Application Imports**: Application runtime code (`src/ruff_sync/core.py`) imports `httpx2` directly (`import httpx2 as httpx`).
23
+ 2. **Elimination of `pytest-httpx2`**: We completely removed `pytest-httpx2` from dependencies due to confusion and transport hook coupling.
24
+ 3. **Test-Guarded Process Aliasing (`src/sitecustomize.py`)**: We place `httpx2.alias_httpx()` inside `src/sitecustomize.py` guarded by a runtime check (`any("pytest" in arg for arg in sys.argv) or "PYTEST_CURRENT_TEST" in os.environ`).
25
+
26
+ ## Rationale & Technical Tradeoffs
27
+
28
+ ### 1. Removal of `pytest-httpx2`
29
+ Beyond introducing transport hook conflicts and entrypoint ordering bugs, `pytest-httpx2` caused developer confusion:
30
+ - `pytest-httpx2` is a wrapper around `respx`, but its name frequently leads developers to the documentation for `pytest-httpx` / `httpx_pytest`.
31
+ - This created confusion around fixture names, routing syntax, and unexpected mocking behavior.
32
+ Eliminating `pytest-httpx2` removes this confusing wrapper layer and standardizes our test suite directly on `respx`.
33
+
34
+ ### 2. No Production HTTP Plugin Dependencies
35
+ `ruff-sync` runtime code does **not** depend on third-party `httpcore` or `httpx` plugins in production.
36
+ If our production runtime relied on external plugins that hard-coded `import httpx` or `import httpcore`, we would require process-wide aliasing at production application startup. Because `ruff-sync` manages its HTTP transport directly via `httpx2`, aliasing is only necessary during test suite execution when `respx` is loaded by Pytest.
37
+
38
+ ### 3. Packaging Exclusion
39
+ `src/sitecustomize.py` lives outside `src/ruff_sync/` and is strictly excluded from wheel packages by Hatchling (`packages = ["src/ruff_sync"]`). Published `.whl` and `sdist` distributions never include or ship `sitecustomize.py` to end users.
40
+
41
+ ### 4. Why Disabling `respx` Autoloading (`-p no:respx`) Is a Gnarly Upstream Problem
42
+ We evaluated disabling `respx` entrypoint autoloading via `addopts = ["-p", "no:respx"]` and manually declaring `pytest_plugins = ["respx"]` in `conftest.py`. However, this reveals a gnarly entrypoint race condition:
43
+ - `respx` registers a `pytest11` entrypoint that imports `httpx`/`httpcore` at plugin discovery time.
44
+ - Disabling `respx` autoloading and re-enabling it via `pytest_plugins` causes `respx` to initialize its `MockRouter` hooks *after* Python module resolution has settled. In end-to-end CLI tests where `httpx2.AsyncClient` is instantiated across sub-threads or CLI entrypoints, `httpx2` creates fresh `httpcore2` transport instances that bypass `respx`'s delayed transport hooks, causing `RESPX: some routes were not called!` errors.
45
+ - **Upstream Resolution Needed**: This conflict needs to be resolved **upstream** — either in `respx` (by deferring transport binding until active router context rather than entrypoint import time) or in `httpx2` (by providing native `httpcore2` transport interceptors for `respx` without requiring `sys.modules` aliasing). Until fixed upstream, `sitecustomize.py` is the only mechanism that forces `alias_httpx()` to run at Python interpreter launch before Pytest's entrypoint scanner initializes `respx`.
46
+
47
+ ## References
48
+
49
+ - [ADR Index](./README.md)
50
+ - [httpx2 Migration Guide](https://httpx2.pydantic.dev/migration/)
51
+ - [Issue #202](https://github.com/Kilo59/ruff-sync/issues/202) / [PR #203](https://github.com/Kilo59/ruff-sync/pull/203)
@@ -7,6 +7,7 @@ This is an internal record of architectural decisions for `ruff-sync`. These doc
7
7
  | [0001](./0001-type-refactoring-strategy.md) | 2026-04-05 | Accepted | Type Refactoring Strategy |
8
8
  | [0002](./0002-tui-node-ast.md) | 2026-04-05 | Accepted | TUI Node AST Architecture |
9
9
  | [0003](./0003-argument-resolution-layers.md) | 2026-04-10 | Accepted | Two-Layer Argument Resolution (Transport vs Execution) |
10
+ | [0004](./0004-httpx2-respx-test-aliasing.md) | 2026-07-25 | Accepted | httpx2 Integration, Respx Mocking, and Test-Guarded Process Aliasing |
10
11
 
11
12
  ---
12
13
  *For instructions on how to create or manage ADRs, see the [ADR Skill](../skills/adr/SKILL.md).*
@@ -58,8 +58,8 @@ def error(
58
58
  message: str,
59
59
  file_path: pathlib.Path | None = None,
60
60
  logger: logging.Logger | None = None,
61
- check_name: str = "ruff-sync/config-drift", # machine-readable rule ID
62
- drift_key: str | None = None, # e.g. "lint.select"
61
+ check_name: str = "ruff-sync/config-drift", # machine-readable rule ID
62
+ drift_key: str | None = None, # e.g. "lint.select"
63
63
  ) -> None: ...
64
64
  ```
65
65
 
@@ -107,11 +107,11 @@ this even when `UpstreamError` or another exception occurs.
107
107
  1. **Add the format value** to `OutputFormat` in `src/ruff_sync/constants.py`:
108
108
  ```python
109
109
  class OutputFormat(str, enum.Enum):
110
- TEXT = "text"
111
- JSON = "json"
110
+ TEXT = "text"
111
+ JSON = "json"
112
112
  GITHUB = "github"
113
113
  GITLAB = "gitlab"
114
- SARIF = "sarif" # new
114
+ SARIF = "sarif" # new
115
115
  ```
116
116
 
117
117
  2. **Implement the class** in `src/ruff_sync/formatters.py`. For a
@@ -151,7 +151,7 @@ fmt = get_formatter(args.output_format)
151
151
  try:
152
152
  ...
153
153
  finally:
154
- fmt.finalize() # no-op for streaming; flushes JSON for accumulating
154
+ fmt.finalize() # no-op for streaming; flushes JSON for accumulating
155
155
  ```
156
156
 
157
157
  `finalize()` is always called unconditionally — **do not** guard it with
@@ -168,6 +168,7 @@ is newly introduced or already resolved between branches.
168
168
  ```python
169
169
  import hashlib
170
170
 
171
+
171
172
  def _make_fingerprint(upstream_url: str, local_file: str, drift_key: str | None) -> str:
172
173
  if drift_key:
173
174
  raw = f"ruff-sync:drift:{upstream_url}:{local_file}:{drift_key}"
@@ -145,6 +145,7 @@ For a "config drift" issue per key (e.g., `lint.select`), a good stable fingerpr
145
145
  ```python
146
146
  import hashlib
147
147
 
148
+
148
149
  def make_fingerprint(upstream_url: str, local_file: str, drift_key: str) -> str:
149
150
  """Stable fingerprint for a config drift issue."""
150
151
  raw = f"ruff-sync:drift:{upstream_url}:{local_file}:{drift_key}"
@@ -281,13 +282,15 @@ class GitlabFormatter:
281
282
  fingerprint: str | None = None,
282
283
  ) -> None:
283
284
  (logger or LOGGER).error(message)
284
- self._issues.append(self._make_issue(
285
- description=message,
286
- check_name=check_name,
287
- severity="major",
288
- file_path=file_path,
289
- fingerprint=fingerprint,
290
- ))
285
+ self._issues.append(
286
+ self._make_issue(
287
+ description=message,
288
+ check_name=check_name,
289
+ severity="major",
290
+ file_path=file_path,
291
+ fingerprint=fingerprint,
292
+ )
293
+ )
291
294
 
292
295
  def warning(
293
296
  self,
@@ -298,13 +301,15 @@ class GitlabFormatter:
298
301
  fingerprint: str | None = None,
299
302
  ) -> None:
300
303
  (logger or LOGGER).warning(message)
301
- self._issues.append(self._make_issue(
302
- description=message,
303
- check_name=check_name,
304
- severity="minor",
305
- file_path=file_path,
306
- fingerprint=fingerprint,
307
- ))
304
+ self._issues.append(
305
+ self._make_issue(
306
+ description=message,
307
+ check_name=check_name,
308
+ severity="minor",
309
+ file_path=file_path,
310
+ fingerprint=fingerprint,
311
+ )
312
+ )
308
313
 
309
314
  def _make_issue(
310
315
  self,
@@ -327,6 +332,7 @@ class GitlabFormatter:
327
332
  @staticmethod
328
333
  def _auto_fingerprint(description: str, path: str) -> str:
329
334
  import hashlib
335
+
330
336
  raw = f"ruff-sync:drift:{path}:{description}"
331
337
  return hashlib.md5(raw.encode()).hexdigest()
332
338
 
@@ -370,7 +376,7 @@ class OutputFormat(str, enum.Enum):
370
376
  TEXT = "text"
371
377
  JSON = "json"
372
378
  GITHUB = "github"
373
- GITLAB = "gitlab" # NEW
379
+ GITLAB = "gitlab" # NEW
374
380
  ```
375
381
 
376
382
  Update `get_formatter` in `formatters.py`:
@@ -530,7 +536,7 @@ fmt = get_formatter(args.output_format)
530
536
  try:
531
537
  ...
532
538
  finally:
533
- fmt.finalize() # Writes the JSON array to stdout (piped to file by CI)
539
+ fmt.finalize() # Writes the JSON array to stdout (piped to file by CI)
534
540
  ```
535
541
 
536
542
  **Do not** guard the call with `isinstance` or `hasattr` checks.
@@ -562,6 +568,7 @@ from unittest.mock import patch
562
568
 
563
569
  from ruff_sync.formatters import GitlabFormatter
564
570
 
571
+
565
572
  def test_gitlab_formatter_empty_on_no_issues(capsys):
566
573
  fmt = GitlabFormatter()
567
574
  fmt.finalize()
@@ -569,6 +576,7 @@ def test_gitlab_formatter_empty_on_no_issues(capsys):
569
576
  issues = json.loads(captured.out)
570
577
  assert issues == []
571
578
 
579
+
572
580
  def test_gitlab_formatter_error_produces_major_issue(capsys):
573
581
  fmt = GitlabFormatter()
574
582
  fmt.error("drift found", file_path=pathlib.Path("pyproject.toml"))
@@ -581,6 +589,7 @@ def test_gitlab_formatter_error_produces_major_issue(capsys):
581
589
  assert issues[0]["location"]["lines"]["begin"] == 1
582
590
  assert "fingerprint" in issues[0]
583
591
 
592
+
584
593
  def test_gitlab_formatter_fingerprint_is_stable(capsys):
585
594
  fmt1 = GitlabFormatter()
586
595
  fmt2 = GitlabFormatter()
@@ -594,6 +603,7 @@ def test_gitlab_formatter_fingerprint_is_stable(capsys):
594
603
  issues2 = json.loads(out2)
595
604
  assert issues1[0]["fingerprint"] == issues2[0]["fingerprint"]
596
605
 
606
+
597
607
  def test_gitlab_formatter_no_bom(capsys):
598
608
  fmt = GitlabFormatter()
599
609
  fmt.finalize()
@@ -17,8 +17,18 @@ class ResultFormatter(Protocol):
17
17
  def note(self, message: str) -> None: ...
18
18
  def info(self, message: str, logger: logging.Logger | None = None) -> None: ...
19
19
  def success(self, message: str) -> None: ...
20
- def error(self, message: str, file_path: pathlib.Path | None = None, logger: logging.Logger | None = None) -> None: ...
21
- def warning(self, message: str, file_path: pathlib.Path | None = None, logger: logging.Logger | None = None) -> None: ...
20
+ def error(
21
+ self,
22
+ message: str,
23
+ file_path: pathlib.Path | None = None,
24
+ logger: logging.Logger | None = None,
25
+ ) -> None: ...
26
+ def warning(
27
+ self,
28
+ message: str,
29
+ file_path: pathlib.Path | None = None,
30
+ logger: logging.Logger | None = None,
31
+ ) -> None: ...
22
32
  def debug(self, message: str, logger: logging.Logger | None = None) -> None: ...
23
33
  def diff(self, diff_text: str) -> None: ...
24
34
  ```
@@ -88,6 +88,7 @@ def validate_toml_syntax(doc: TOMLDocument) -> bool:
88
88
  """
89
89
  try:
90
90
  import tomlkit # already a dep
91
+
91
92
  tomlkit.parse(doc.as_string())
92
93
  return True
93
94
  except Exception: # noqa: BLE001
@@ -146,9 +147,7 @@ def validate_ruff_accepts_config(doc: TOMLDocument, is_ruff_toml: bool = False)
146
147
  )
147
148
  return False
148
149
  except FileNotFoundError:
149
- LOGGER.warning(
150
- "⚠️ `ruff` not found on PATH — skipping Ruff config validation."
151
- )
150
+ LOGGER.warning("⚠️ `ruff` not found on PATH — skipping Ruff config validation.")
152
151
  return True # Soft fail: don't block if ruff isn't installed
153
152
  except subprocess.TimeoutExpired:
154
153
  LOGGER.warning("⚠️ Ruff config validation timed out — skipping.")
@@ -184,7 +183,7 @@ Validation is **opt-in**. Before touching `core.py`, wire up the CLI flag.
184
183
  class Arguments(NamedTuple):
185
184
  ...
186
185
  validate: bool = False # run --validate checks before writing
187
- strict: bool = False # treat warnings as errors (implies validate)
186
+ strict: bool = False # treat warnings as errors (implies validate)
188
187
  ```
189
188
 
190
189
  **In `common_parser`** (around line 236), add:
@@ -230,18 +229,17 @@ Open `core.py`. Find the `pull()` function (around line 1103). Look for this blo
230
229
  **After** this block (and **before** `should_save = args.save ...`), insert:
231
230
 
232
231
  ```python
233
- # Validation is opt-in — only run if --validate (or --strict) was passed
234
- if args.validate:
235
- is_ruff_toml = is_ruff_toml_file(_source_toml_path.name)
236
- from ruff_sync.validation import validate_merged_config # noqa: PLC0415
237
- if not validate_merged_config(
238
- source_doc, is_ruff_toml=is_ruff_toml, strict=args.strict
239
- ):
240
- fmt.error(
241
- "❌ Merged config failed validation. Local file left unchanged.",
242
- logger=LOGGER,
243
- )
244
- return 1
232
+ # Validation is opt-in — only run if --validate (or --strict) was passed
233
+ if args.validate:
234
+ is_ruff_toml = is_ruff_toml_file(_source_toml_path.name)
235
+ from ruff_sync.validation import validate_merged_config # noqa: PLC0415
236
+
237
+ if not validate_merged_config(source_doc, is_ruff_toml=is_ruff_toml, strict=args.strict):
238
+ fmt.error(
239
+ "❌ Merged config failed validation. Local file left unchanged.",
240
+ logger=LOGGER,
241
+ )
242
+ return 1
245
243
  ```
246
244
 
247
245
  > **Note**: The inline import avoids a circular import issue if `validation.py` ever needs to
@@ -437,6 +435,7 @@ Add to `tests/test_config_validation.py`:
437
435
  ```python
438
436
  def test_version_consistency_warn_on_mismatch(caplog: pytest.LogCaptureFixture) -> None:
439
437
  import logging
438
+
440
439
  doc = tomlkit.parse(
441
440
  '[project]\nrequires-python = ">=3.10"\n\n[tool.ruff]\ntarget-version = "py39"\n'
442
441
  )
@@ -492,9 +491,7 @@ def _get_deprecated_rule_codes() -> frozenset[str]:
492
491
  if result.returncode != 0:
493
492
  return frozenset()
494
493
  rules = json.loads(result.stdout)
495
- return frozenset(
496
- r["code"] for r in rules if r.get("deprecated") is True
497
- )
494
+ return frozenset(r["code"] for r in rules if r.get("deprecated") is True)
498
495
  except (FileNotFoundError, subprocess.TimeoutExpired, json.JSONDecodeError, KeyError):
499
496
  return frozenset()
500
497
  ```
@@ -573,7 +570,9 @@ def check_deprecated_rules(
573
570
  is_ruff_toml: bool = False,
574
571
  _deprecated_codes: frozenset[str] | None = None,
575
572
  ) -> None:
576
- deprecated_codes = _deprecated_codes if _deprecated_codes is not None else _get_deprecated_rule_codes()
573
+ deprecated_codes = (
574
+ _deprecated_codes if _deprecated_codes is not None else _get_deprecated_rule_codes()
575
+ )
577
576
  ...
578
577
  ```
579
578
 
@@ -14,13 +14,16 @@ Instead of asserting on every field manually, compare against a "dirty" object t
14
14
  ```python
15
15
  from dirty_equals import IsInt, IsPartialDict, IsStr
16
16
 
17
+
17
18
  def test_config_logic():
18
19
  result = {"status": "active", "version": 1, "extra": "data"}
19
20
  # Declarative assertion
20
- assert result == IsPartialDict({
21
- "status": IsStr(regex="act.*"),
22
- "version": IsInt(gt=0),
23
- })
21
+ assert result == IsPartialDict(
22
+ {
23
+ "status": IsStr(regex="act.*"),
24
+ "version": IsInt(gt=0),
25
+ }
26
+ )
24
27
  ```
25
28
 
26
29
  ## Detailed reference
@@ -24,11 +24,13 @@ import httpx
24
24
  import pathlib
25
25
 
26
26
  # Match a partial dict with mixed types
27
- assert response_data == IsPartialDict({
28
- "url": IsInstance(httpx.URL),
29
- "status": "success",
30
- "retries": 0,
31
- })
27
+ assert response_data == IsPartialDict(
28
+ {
29
+ "url": IsInstance(httpx.URL),
30
+ "status": "success",
31
+ "retries": 0,
32
+ }
33
+ )
32
34
  ```
33
35
 
34
36
  ### String and Path Matching
@@ -13,7 +13,7 @@ from dirty_equals import IsPartialDict
13
13
  import tomlkit
14
14
 
15
15
  # Parse some TOML
16
- doc = tomlkit.parse('[tool.ruff]\nline-length = 80')
16
+ doc = tomlkit.parse("[tool.ruff]\nline-length = 80")
17
17
 
18
18
  # Match the tool.ruff section
19
19
  ruff_config = doc["tool"]["ruff"]
@@ -46,9 +46,11 @@ args = ruff_sync_cli.Arguments(
46
46
  )
47
47
 
48
48
  # Convert to dict and match specific fields
49
- assert args._asdict() == IsPartialDict({
50
- "command": "pull",
51
- "upstream": (IsInstance(httpx.URL),),
52
- "to": IsInstance(pathlib.Path),
53
- })
49
+ assert args._asdict() == IsPartialDict(
50
+ {
51
+ "command": "pull",
52
+ "upstream": (IsInstance(httpx.URL),),
53
+ "to": IsInstance(pathlib.Path),
54
+ }
55
+ )
54
56
  ```
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: ruff-sync-usage
2
+ name: ruff-sync
3
3
  description: >-
4
4
  Configure and operate ruff-sync to synchronize Ruff linter settings across Python projects.
5
5
  Use when the user wants to set up ruff-sync, sync Ruff config from an upstream source,
@@ -14,8 +14,10 @@ from __future__ import annotations
14
14
  from textual.app import App, ComposeResult
15
15
  from textual.widgets import Header, Footer, Static
16
16
 
17
+
17
18
  class SimpleApp(App[None]):
18
19
  """A minimal Textual app."""
20
+
19
21
  BINDINGS = [("q", "quit", "Quit")]
20
22
 
21
23
  def compose(self) -> ComposeResult:
@@ -23,6 +25,7 @@ class SimpleApp(App[None]):
23
25
  yield Static("Hello, [bold blue]Textual[/bold blue]!")
24
26
  yield Footer()
25
27
 
28
+
26
29
  if __name__ == "__main__":
27
30
  SimpleApp().run()
28
31
  ```
@@ -33,6 +33,7 @@ Define `reactive` attributes to automatically trigger updates. Use `watch_<attri
33
33
  ```python
34
34
  from textual.reactive import reactive
35
35
 
36
+
36
37
  class Counter(Static):
37
38
  # Reactive state: UI updates whenever 'count' is modified
38
39
  count: reactive[int] = reactive(0)
@@ -52,6 +53,8 @@ Widgets can communicate with parents via custom messages:
52
53
  1. **Define a Message**:
53
54
  ```python
54
55
  from textual.message import Message
56
+
57
+
55
58
  class DataLoaded(Message):
56
59
  def __init__(self, data: list[str]) -> None:
57
60
  self.data = data
@@ -10,6 +10,7 @@ Use the `pilot` object to interact with your app in a headless state.
10
10
  import pytest
11
11
  from my_app import SimpleApp
12
12
 
13
+
13
14
  @pytest.mark.asyncio
14
15
  async def test_button_click():
15
16
  app = SimpleApp()
@@ -82,6 +82,7 @@ Modals are screens with a transparent or dim background that overlay the main ap
82
82
  from textual.screen import ModalScreen
83
83
  from textual.app import App
84
84
 
85
+
85
86
  class OmniboxScreen(ModalScreen[str]):
86
87
  # A modal screen that returns a `str` when dismissed.
87
88
  def compose(self) -> ComposeResult:
@@ -92,11 +93,13 @@ class OmniboxScreen(ModalScreen[str]):
92
93
  def on_input_submitted(self, event: Input.Submitted) -> None:
93
94
  self.dismiss(event.value)
94
95
 
96
+
95
97
  # In the main App or Screen:
96
98
  def on_key(self, event: events.Key) -> None:
97
99
  if event.key == "ctrl+p":
98
100
  self.push_screen(OmniboxScreen(), self.handle_omnibox_result)
99
101
 
102
+
100
103
  def handle_omnibox_result(self, result: str | None) -> None:
101
104
  if result:
102
105
  print(f"Selected: {result}")
@@ -19,9 +19,11 @@ Use `TypeIs` when you want to definitively split a Union.
19
19
  from typing import Union
20
20
  from typing_extensions import TypeIs
21
21
 
22
+
22
23
  def is_str(val: Union[str, int]) -> TypeIs[str]:
23
24
  return isinstance(val, str)
24
25
 
26
+
25
27
  def process(val: Union[str, int]) -> None:
26
28
  if is_str(val):
27
29
  # Mypy knows val is str
@@ -39,10 +41,12 @@ Use `@runtime_checkable Protocol` with `TypeIs` for structural subtyping.
39
41
  from typing import Protocol, runtime_checkable
40
42
  from typing_extensions import TypeIs
41
43
 
44
+
42
45
  @runtime_checkable
43
46
  class Reader(Protocol):
44
47
  def read(self) -> str: ...
45
48
 
49
+
46
50
  def is_reader(val: object) -> TypeIs[Reader]:
47
51
  return isinstance(val, Reader)
48
52
  ```
@@ -17,8 +17,8 @@ Match the Mypy error code from your terminal to the project-safe resolution patt
17
17
 
18
18
  **Bad (Cast)**:
19
19
  ```python
20
- val = get_union() # str | None
21
- val.upper() # [union-attr] "None" has no attribute "upper"
20
+ val = get_union() # str | None
21
+ val.upper() # [union-attr] "None" has no attribute "upper"
22
22
  # NO: val = cast(str, val)
23
23
  ```
24
24
 
@@ -26,7 +26,7 @@ val.upper() # [union-attr] "None" has no attribute "upper"
26
26
  ```python
27
27
  val = get_union()
28
28
  if val is not None:
29
- val.upper() # FIXED
29
+ val.upper() # FIXED
30
30
  ```
31
31
 
32
32
  ### Fixing `[assignment]` (Dealing with `tomlkit` or `Any`)
@@ -34,7 +34,7 @@ if val is not None:
34
34
  **Bad (Cast)**:
35
35
  ```python
36
36
  doc = tomlkit.parse(...)
37
- tool: Table = doc["tool"] # [assignment] Incompatible types (Item vs Table)
37
+ tool: Table = doc["tool"] # [assignment] Incompatible types (Item vs Table)
38
38
  # NO: tool = cast(Table, doc["tool"])
39
39
  ```
40
40
 
@@ -52,8 +52,10 @@ if not isinstance(tool, Table):
52
52
  **Bad (Cast)**:
53
53
  ```python
54
54
  def process_str(s: str): ...
55
+
56
+
55
57
  data: Union[str, int] = ...
56
- process_str(data) # [arg-type] Argument 1 has incompatible type
58
+ process_str(data) # [arg-type] Argument 1 has incompatible type
57
59
  # NO: process_str(cast(str, data))
58
60
  ```
59
61
 
@@ -61,9 +63,11 @@ process_str(data) # [arg-type] Argument 1 has incompatible type
61
63
  ```python
62
64
  from typing_extensions import TypeIs
63
65
 
66
+
64
67
  def is_str(v: object) -> TypeIs[str]:
65
68
  return isinstance(v, str)
66
69
 
70
+
67
71
  if is_str(data):
68
- process_str(data) # FIXED
72
+ process_str(data) # FIXED
69
73
  ```