ruff-sync 0.1.6.dev3__tar.gz → 0.1.7.dev1__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 (208) hide show
  1. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/TESTING.md +1 -1
  2. ruff_sync-0.1.7.dev1/.agents/args_refactor_plan.md +443 -0
  3. ruff_sync-0.1.7.dev1/.agents/cli_animation_plan.md +710 -0
  4. ruff_sync-0.1.7.dev1/.agents/decisions/0003-argument-resolution-layers.md +120 -0
  5. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/decisions/README.md +1 -0
  6. ruff_sync-0.1.7.dev1/.agents/docs_update_plan.md +384 -0
  7. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/ruff-sync-usage/references/ci-integration.md +13 -2
  8. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/ruff-sync-usage/references/configuration.md +24 -0
  9. ruff_sync-0.1.7.dev1/.agents/workflows/update-recordings.md +72 -0
  10. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/PKG-INFO +5 -1
  11. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/README.md +4 -0
  12. ruff_sync-0.1.7.dev1/docs/assets/recordings/check_drift.gif +0 -0
  13. ruff_sync-0.1.7.dev1/docs/assets/recordings/check_in_sync.gif +0 -0
  14. ruff_sync-0.1.7.dev1/docs/assets/recordings/help_overview.gif +0 -0
  15. ruff_sync-0.1.7.dev1/docs/assets/recordings/init_project.gif +0 -0
  16. ruff_sync-0.1.7.dev1/docs/assets/recordings/pull_basic.gif +0 -0
  17. ruff_sync-0.1.7.dev1/docs/assets/recordings/validate_strict.gif +0 -0
  18. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/ci-integration.md +33 -2
  19. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/configuration.md +19 -0
  20. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/examples/advanced-config.toml +3 -0
  21. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/index.md +5 -0
  22. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/pre-commit.md +3 -3
  23. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/pre-defined-configs.md +4 -4
  24. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/troubleshooting.md +43 -0
  25. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/usage.md +10 -2
  26. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/pyproject.toml +1 -1
  27. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/cli.py +131 -37
  28. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/constants.py +41 -3
  29. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/core.py +34 -17
  30. ruff_sync-0.1.7.dev1/tapes/_common.tape +21 -0
  31. ruff_sync-0.1.7.dev1/tapes/check_drift.tape +38 -0
  32. ruff_sync-0.1.7.dev1/tapes/check_in_sync.tape +20 -0
  33. ruff_sync-0.1.7.dev1/tapes/help_overview.tape +27 -0
  34. ruff_sync-0.1.7.dev1/tapes/init_project.tape +36 -0
  35. ruff_sync-0.1.7.dev1/tapes/pull_basic.tape +32 -0
  36. ruff_sync-0.1.7.dev1/tapes/validate_strict.tape +24 -0
  37. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tasks.py +55 -0
  38. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/conftest.py +17 -1
  39. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_basic.py +2 -2
  40. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_config_validation.py +315 -0
  41. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_constants.py +30 -1
  42. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_formatters.py +14 -6
  43. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_serialization.py +148 -13
  44. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/uv.lock +1 -1
  45. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/DEPENDENCIES.md +0 -0
  46. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/decisions/0001-type-refactoring-strategy.md +0 -0
  47. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/decisions/0002-tui-node-ast.md +0 -0
  48. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/doc-fix.md +0 -0
  49. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/formatters-architecture.md +0 -0
  50. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/gitlab-reports.md +0 -0
  51. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/issue-102-context.md +0 -0
  52. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/plans/issue-100-roadmap-plan.md +0 -0
  53. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/plans/issue-116-config-validation.md +0 -0
  54. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/ruff.toml +0 -0
  55. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/adr/SKILL.md +0 -0
  56. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/dirty-equals/SKILL.md +0 -0
  57. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/dirty-equals/references/common-matchers.md +0 -0
  58. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/dirty-equals/references/toml-matching.md +0 -0
  59. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/dirty-equals/trigger_eval.json +0 -0
  60. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/gh-issues/SKILL.md +0 -0
  61. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/mike/SKILL.md +0 -0
  62. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/mike/references/commands.md +0 -0
  63. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/mkdocs-generation/SKILL.md +0 -0
  64. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/mkdocs-generation/examples.md +0 -0
  65. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/mkdocs-generation/templates/api-reference.md +0 -0
  66. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/mkdocs-generation/templates/getting-started.md +0 -0
  67. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/mkdocs-generation/templates/index.md +0 -0
  68. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/mkdocs-generation/templates/mkdocs.yml +0 -0
  69. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/release-notes-generation/SKILL.md +0 -0
  70. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/ruff-sync-usage/SKILL.md +0 -0
  71. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/ruff-sync-usage/references/troubleshooting.md +0 -0
  72. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/skill-creator/SKILL.md +0 -0
  73. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/skill-creator/references/best-practices.md +0 -0
  74. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/skill-creator/references/evaluating-skills.md +0 -0
  75. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/skill-creator/references/optimizing-descriptions.md +0 -0
  76. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/skill-creator/references/quickstart.md +0 -0
  77. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/skill-creator/references/sources.md +0 -0
  78. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/skill-creator/references/using-scripts.md +0 -0
  79. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/skill-creator/scripts/scaffold_skill.py +0 -0
  80. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/textual/SKILL.md +0 -0
  81. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/textual/examples/basic_app.py +0 -0
  82. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/textual/examples/reactive_example.py +0 -0
  83. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/textual/references/events.md +0 -0
  84. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/textual/references/styling.md +0 -0
  85. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/textual/references/testing.md +0 -0
  86. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/textual/references/widgets.md +0 -0
  87. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/type-checking/SKILL.md +0 -0
  88. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/type-checking/evals.json +0 -0
  89. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/type-checking/references/advanced-narrowing.md +0 -0
  90. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/type-checking/references/error-code-lookup.md +0 -0
  91. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/type-checking/references/generics.md +0 -0
  92. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/type-checking/references/naming.md +0 -0
  93. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/type-checking/references/protocol-patterns.md +0 -0
  94. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/type-checking/references/quickstart.md +0 -0
  95. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/type-checking/references/refactoring-patterns.md +0 -0
  96. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/type-checking/scripts/audit_types.py +0 -0
  97. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/skills/warnings-control/SKILL.md +0 -0
  98. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/tui_design.md +0 -0
  99. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/tui_requirements.md +0 -0
  100. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/tui_rule_browsing.md +0 -0
  101. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/tui_rule_browsing_design.md +0 -0
  102. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/workflows/add-test-case.md +0 -0
  103. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.agents/workflows/update-screenshots.md +0 -0
  104. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.git-blame-ignore-revs +0 -0
  105. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.github/dependabot.yml +0 -0
  106. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.github/workflows/ci.yaml +0 -0
  107. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.github/workflows/complexity.yaml +0 -0
  108. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.gitignore +0 -0
  109. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.pre-commit-config.yaml +0 -0
  110. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/.pre-commit-hooks.yaml +0 -0
  111. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/AGENTS.md +0 -0
  112. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/CONTRIBUTING.md +0 -0
  113. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/LICENSE.md +0 -0
  114. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/codecov.yml +0 -0
  115. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/configs/data-science-engineering/ruff.toml +0 -0
  116. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/configs/fastapi/ruff.toml +0 -0
  117. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/configs/kitchen-sink/ruff.toml +0 -0
  118. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/agent-skill.md +0 -0
  119. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/assets/favicon.png +0 -0
  120. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/assets/github-job-summary.png +0 -0
  121. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/assets/github-pr-annotation.png +0 -0
  122. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/assets/inspect-main.png +0 -0
  123. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/assets/inspect-search.png +0 -0
  124. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/assets/logo.png +0 -0
  125. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/assets/ruff_sync_banner.png +0 -0
  126. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/assets/screenshots/dashboard.svg +0 -0
  127. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/assets/screenshots/legend_help.svg +0 -0
  128. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/assets/screenshots/rule_details.svg +0 -0
  129. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/assets/screenshots/screenshot_sample.toml +0 -0
  130. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/assets/screenshots/search_omnibox.svg +0 -0
  131. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/best-practices.md +0 -0
  132. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/contributing.md +0 -0
  133. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/examples/basic-config.toml +0 -0
  134. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/gen_ref_pages.py +0 -0
  135. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/inspect.md +0 -0
  136. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/installation.md +0 -0
  137. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/overrides/main.html +0 -0
  138. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/overrides/partials/version_warning.html +0 -0
  139. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/docs/url-resolution.md +0 -0
  140. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/mkdocs.yml +0 -0
  141. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/scripts/check_dogfood.sh +0 -0
  142. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/scripts/generate_tui_screenshots.py +0 -0
  143. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/scripts/gitclone_dogfood.sh +0 -0
  144. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/scripts/pull_dogfood.sh +0 -0
  145. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/skills-lock.json +0 -0
  146. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/__init__.py +0 -0
  147. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/__main__.py +0 -0
  148. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/config_io.py +0 -0
  149. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/dependencies.py +0 -0
  150. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/formatters.py +0 -0
  151. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/pre_commit.py +0 -0
  152. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/system.py +0 -0
  153. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/tui/__init__.py +0 -0
  154. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/tui/app.py +0 -0
  155. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/tui/constants.py +0 -0
  156. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/tui/screens.py +0 -0
  157. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/tui/themes.py +0 -0
  158. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/tui/types_.py +0 -0
  159. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/tui/widgets.py +0 -0
  160. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/types_.py +0 -0
  161. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/src/ruff_sync/validation.py +0 -0
  162. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/__init__.py +0 -0
  163. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/multi_upstream_final.toml +0 -0
  164. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/multi_upstream_initial.toml +0 -0
  165. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/multi_upstream_up1.toml +0 -0
  166. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/multi_upstream_up2.toml +0 -0
  167. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/no_changes_final.toml +0 -0
  168. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/no_changes_initial.toml +0 -0
  169. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/no_changes_upstream.toml +0 -0
  170. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/no_dotted_keys_final.toml +0 -0
  171. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/no_dotted_keys_initial.toml +0 -0
  172. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/no_dotted_keys_upstream.toml +0 -0
  173. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/no_ruff_cfg_final.toml +0 -0
  174. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/no_ruff_cfg_initial.toml +0 -0
  175. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/no_ruff_cfg_upstream.toml +0 -0
  176. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/readme_excludes_final.toml +0 -0
  177. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/readme_excludes_initial.toml +0 -0
  178. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/readme_excludes_upstream.toml +0 -0
  179. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/standard_final.toml +0 -0
  180. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/standard_initial.toml +0 -0
  181. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/lifecycle_tomls/standard_upstream.toml +0 -0
  182. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/ruff.toml +0 -0
  183. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_check.py +0 -0
  184. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_ci_integration.py +0 -0
  185. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_ci_validation.py +0 -0
  186. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_config_io.py +0 -0
  187. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_corner_cases.py +0 -0
  188. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_dependencies.py +0 -0
  189. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_deprecation.py +0 -0
  190. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_e2e.py +0 -0
  191. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_git_fetch.py +0 -0
  192. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_minimal_imports.sh +0 -0
  193. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_pre_commit.py +0 -0
  194. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_project.py +0 -0
  195. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_rule_logic.py +0 -0
  196. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_scaffold.py +0 -0
  197. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_system.py +0 -0
  198. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_toml_operations.py +0 -0
  199. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_url_handling.py +0 -0
  200. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/test_whitespace.py +0 -0
  201. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/tui/__init__.py +0 -0
  202. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/tui/conftest.py +0 -0
  203. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/tui/test_themes.py +0 -0
  204. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/tui/test_tui.py +0 -0
  205. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/tui/test_tui_types.py +0 -0
  206. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/w_ruff_sync_cfg/pyproject.toml +0 -0
  207. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/wo_ruff_cfg/pyproject.toml +0 -0
  208. {ruff_sync-0.1.6.dev3 → ruff_sync-0.1.7.dev1}/tests/wo_ruff_sync_cfg/pyproject.toml +0 -0
@@ -5,7 +5,7 @@ This document defines the mandatory testing standards and patterns for the `ruff
5
5
  ## 1. Core Principles
6
6
 
7
7
  - **Every Fix Needs a Test**: Any bug fix must include a reproduction test that fails without the fix and passes with it.
8
- - **No Side Effects**: Tests must be isolated and not touch the actual filesystem or make real network calls.
8
+ - **No Side Effects**: Tests must be isolated and not touch the actual filesystem or make real network calls. Furthermore, tests must not pollute the CI environment (e.g., writing to `GITHUB_STEP_SUMMARY`). Always mock CI-specific environment variables using `monkeypatch.delenv` if the code under test interacts with them.
9
9
  - **Semantic + Structural Assertions**: When testing TOML merges, always verify **both**:
10
10
  1. **Structural/Whitespace**: The file "looks" correct (comments and spacing are preserved).
11
11
  2. **Semantic**: The actual data in the merged result matches the expected values. Use the [dirty-equals](skills/dirty-equals/SKILL.md) Agent Skill for declarative, concise assertions.
@@ -0,0 +1,443 @@
1
+ # Refactoring Plan: Clean Up `strict` / `validate` / `pre_commit` Default Handling
2
+
3
+ > **Context**: Commit `051ca28` ("low effort handling of strict/validate defaults") changed the
4
+ > `Arguments` NamedTuple to use `bool | MissingType` for `validate`, `strict`, and `pre_commit`,
5
+ > then forced every consumer (`pull`, `check`, `_merge_multiple_upstreams`) to call
6
+ > `resolve_defaults()` at the top of the function and destructure a 6-tuple. This is architecturally
7
+ > bad for several reasons outlined below.
8
+
9
+ ---
10
+
11
+ ## Problem Analysis
12
+
13
+ ### What the commit did
14
+
15
+ 1. Changed `Arguments.validate`, `Arguments.strict`, and `Arguments.pre_commit` from `bool` to
16
+ `bool | MissingType`.
17
+ 2. Expanded `resolve_defaults()` from `(branch, path, exclude) → 3-tuple` to
18
+ `(branch, path, exclude, validate, strict, pre_commit) → 6-tuple`, mixing two concerns (URL
19
+ resolution defaults and boolean flag defaults).
20
+ 3. Added `resolve_defaults()` calls to the top of `pull()`, `check()`, and
21
+ `_merge_multiple_upstreams()`, each destructuring the 6-tuple with throw-away `_` variables.
22
+ 4. Added `pre_commit` to `ResolvedArgs`, making it carry `bool | MissingType` too.
23
+
24
+ ### Why this is bad
25
+
26
+ | Issue | Explanation |
27
+ |-------|-------------|
28
+ | **Sentinel leaks into execution logic** | `pull()` and `check()` don't care about `MISSING`—they just need a `bool`. But now their type signatures lie: `args.validate` is `bool \| MissingType`, so every access site needs a guard or a `resolve_defaults()` wrapper. |
29
+ | **Duplicated resolution** | `resolve_defaults()` is called 3 times (in `pull`, `check`, `_merge_multiple_upstreams`) with the same args. Each call independently resolves the same values. |
30
+ | **Bloated `resolve_defaults()`** | This function's original purpose was URL parameter defaults (branch, path, exclude). Boolean flags have nothing to do with URL resolution—they were bolted on. |
31
+ | **6-tuple destructuring** | `(branch, path, exclude, validate, strict, pre_commit) = resolve_defaults(...)` is fragile and unreadable. Adding one more field breaks every call site. |
32
+ | **Two representations, unclear boundary** | `Arguments` now holds `MISSING` values, but the CLI layer already resolved them in `_resolve_validate()` / `_resolve_strict()`. So `MISSING` can appear in `Arguments` from two sources: (a) the CLI resolver returned it, or (b) direct construction in tests. The semantics are unclear. |
33
+
34
+ ### The real requirement
35
+
36
+ The `MISSING` sentinel is needed for **serialization only** (`serialize_ruff_sync_config`): when
37
+ `--save` is used, we must distinguish "user explicitly passed `--no-strict`" (serialize
38
+ `strict = false`) from "user said nothing about strict" (don't serialize the key at all). Execution
39
+ logic (`pull`, `check`) just needs a plain `bool`.
40
+
41
+ ---
42
+
43
+ ## Proposed Design
44
+
45
+ **Core idea**: Separate the _transport/serialization_ concern (`MISSING` awareness) from the
46
+ _execution_ concern (plain bools).
47
+
48
+ ### Layer 1: `Arguments` (transport + serialization)
49
+
50
+ Keep `bool | MissingType` for `validate`, `strict`, and `pre_commit` here. This is the type that
51
+ `serialize_ruff_sync_config()` consumes. It is constructed _once_ in `main()` and passed to
52
+ `serialize_ruff_sync_config()` via `pull()`.
53
+
54
+ ### Layer 2: `ExecutionArgs` (new NamedTuple — execution logic)
55
+
56
+ A **new** NamedTuple with **only plain `bool`** for `validate`, `strict`, and `pre_commit`. This is
57
+ what `pull()` and `check()` (and `_merge_multiple_upstreams`) actually use for control flow. It is
58
+ derived from `Arguments` via a single call to a new `Arguments.resolve()` method (or a standalone
59
+ `resolve_execution_args()` function).
60
+
61
+ ```
62
+ CLI (argparse) ──► _resolve_args() ──► main() builds Arguments
63
+
64
+ ┌───────────────────┤
65
+ ▼ ▼
66
+ serialize_ruff_sync_config Arguments.resolve()
67
+ (reads MISSING to decide ──► ExecutionArgs
68
+ what to serialize) (plain bools)
69
+
70
+ ┌──────────┼──────────┐
71
+ ▼ ▼ ▼
72
+ pull() check() _merge_multiple_upstreams()
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Step-by-Step Implementation
78
+
79
+ ### Step 1: Create `ExecutionArgs` NamedTuple
80
+
81
+ **File**: `src/ruff_sync/cli.py`
82
+
83
+ Add a new NamedTuple right after the existing `Arguments` class:
84
+
85
+ ```python
86
+ class ExecutionArgs(NamedTuple):
87
+ """Resolved arguments for execution logic — all sentinels replaced with concrete values."""
88
+
89
+ command: str
90
+ upstream: tuple[URL, ...]
91
+ to: pathlib.Path
92
+ exclude: Iterable[str]
93
+ verbose: int
94
+ branch: str
95
+ path: str | None
96
+ semantic: bool
97
+ diff: bool
98
+ init: bool
99
+ pre_commit: bool # plain bool — MISSING resolved to default
100
+ save: bool | None
101
+ output_format: OutputFormat
102
+ validate: bool # plain bool — MISSING resolved to default
103
+ strict: bool # plain bool — MISSING resolved to default
104
+ ```
105
+
106
+ > [!NOTE]
107
+ > `save` stays `bool | None` because `None` has meaningful semantics there (trigger
108
+ > from `--init`), and it's already resolved before execution.
109
+
110
+ ### Step 2: Add a `resolve()` method to `Arguments`
111
+
112
+ **File**: `src/ruff_sync/cli.py`
113
+
114
+ Add a method to `Arguments` that produces an `ExecutionArgs`:
115
+
116
+ ```python
117
+ class Arguments(NamedTuple):
118
+ # ... existing fields unchanged ...
119
+
120
+ def resolve(self) -> ExecutionArgs:
121
+ """Resolve all MISSING sentinels to their effective defaults for execution."""
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
126
+ self.validate,
127
+ self.strict,
128
+ self.pre_commit,
129
+ )
130
+ return ExecutionArgs(
131
+ command=self.command,
132
+ upstream=self.upstream,
133
+ to=self.to,
134
+ exclude=self.exclude,
135
+ verbose=self.verbose,
136
+ branch=self.branch,
137
+ path=self.path,
138
+ semantic=self.semantic,
139
+ diff=self.diff,
140
+ init=self.init,
141
+ pre_commit=eff_pre_commit,
142
+ save=self.save,
143
+ output_format=self.output_format,
144
+ validate=eff_validate,
145
+ strict=eff_strict,
146
+ )
147
+ ```
148
+
149
+ > [!IMPORTANT]
150
+ > We still use `resolve_defaults()` for the boolean resolution logic (strict implies
151
+ > validate, defaults), but only call it **once** and only for the boolean fields.
152
+
153
+ ### Step 3: Restore `resolve_defaults()` to its original scope
154
+
155
+ **File**: `src/ruff_sync/constants.py`
156
+
157
+ Revert `resolve_defaults()` to its original 3-parameter, 3-return form for URL parameter defaults.
158
+ Extract boolean resolution into a separate function:
159
+
160
+ ```python
161
+ def resolve_defaults(
162
+ branch: str | MissingType,
163
+ path: str | None | MissingType,
164
+ exclude: Iterable[str] | MissingType,
165
+ ) -> tuple[str, str | None, Iterable[str]]:
166
+ """Resolve MISSING sentinel values to their effective defaults.
167
+
168
+ This is the single source of truth for MISSING → default resolution for
169
+ URL-related parameters (branch, path, exclude).
170
+ """
171
+ eff_branch = branch if branch is not MISSING else DEFAULT_BRANCH
172
+ raw_path = path if path is not MISSING else DEFAULT_PATH
173
+ eff_path: str | None = raw_path or None
174
+ eff_exclude = exclude if exclude is not MISSING else DEFAULT_EXCLUDE
175
+ return eff_branch, eff_path, eff_exclude
176
+
177
+
178
+ def resolve_bool_flags(
179
+ validate: bool | MissingType = MISSING,
180
+ strict: bool | MissingType = MISSING,
181
+ pre_commit: bool | MissingType = MISSING,
182
+ ) -> tuple[bool, bool, bool]:
183
+ """Resolve MISSING sentinel values for boolean execution flags.
184
+
185
+ Returns:
186
+ A ``(validate, strict, pre_commit)`` tuple with defaults applied.
187
+ ``strict=True`` implicitly enables ``validate``.
188
+ """
189
+ eff_strict = strict if strict is not MISSING else False
190
+ eff_validate = (validate if validate is not MISSING else False) or eff_strict
191
+ eff_pre_commit = pre_commit if pre_commit is not MISSING else True
192
+ return eff_validate, eff_strict, eff_pre_commit
193
+ ```
194
+
195
+ > [!TIP]
196
+ > This is a clean separation: `resolve_defaults` handles URL stuff, `resolve_bool_flags`
197
+ > handles boolean stuff. Neither grows unbounded.
198
+
199
+ ### Step 4: Update `Arguments.resolve()` to use `resolve_bool_flags`
200
+
201
+ **File**: `src/ruff_sync/cli.py`
202
+
203
+ Replace the `resolve_defaults()` call in `Arguments.resolve()` with `resolve_bool_flags()`:
204
+
205
+ ```python
206
+ from ruff_sync.constants import resolve_bool_flags
207
+
208
+ class Arguments(NamedTuple):
209
+ # ...
210
+ def resolve(self) -> ExecutionArgs:
211
+ eff_validate, eff_strict, eff_pre_commit = resolve_bool_flags(
212
+ self.validate, self.strict, self.pre_commit,
213
+ )
214
+ return ExecutionArgs(
215
+ command=self.command,
216
+ upstream=self.upstream,
217
+ to=self.to,
218
+ exclude=self.exclude,
219
+ verbose=self.verbose,
220
+ branch=self.branch,
221
+ path=self.path,
222
+ semantic=self.semantic,
223
+ diff=self.diff,
224
+ init=self.init,
225
+ pre_commit=eff_pre_commit,
226
+ save=self.save,
227
+ output_format=self.output_format,
228
+ validate=eff_validate,
229
+ strict=eff_strict,
230
+ )
231
+ ```
232
+
233
+ ### Step 5: Update `pull()` to use `ExecutionArgs`
234
+
235
+ **File**: `src/ruff_sync/core.py`
236
+
237
+ Remove the `resolve_defaults()` call at the top of `pull()`. Instead, accept
238
+ **both** `Arguments` (for serialization) and call `.resolve()` once at the top:
239
+
240
+ ```python
241
+ async def pull(args: Arguments) -> int:
242
+ exec_args = args.resolve() # single resolution point
243
+
244
+ # Use exec.validate, exec.strict, exec.pre_commit for logic
245
+ # Use args (original, with MISSING) only for serialize_ruff_sync_config()
246
+
247
+ ...
248
+ if exec.validate:
249
+ ...validate_merged_config(..., strict=exec.strict, exclude=exec.exclude)
250
+
251
+ ...
252
+ if should_save:
253
+ serialize_ruff_sync_config(source_doc, args) # ← still uses raw args
254
+
255
+ ...
256
+ if exec.pre_commit:
257
+ sync_pre_commit(...)
258
+ ```
259
+
260
+ > [!WARNING]
261
+ > `serialize_ruff_sync_config()` **must** still receive the original `Arguments` (with
262
+ > `MISSING`) so it knows which flags to write and which to omit. Do NOT pass `ExecutionArgs` here.
263
+
264
+ ### Step 6: Update `check()` to use `ExecutionArgs`
265
+
266
+ **File**: `src/ruff_sync/core.py`
267
+
268
+ Same pattern: call `args.resolve()` once, use the result for execution:
269
+
270
+ ```python
271
+ async def check(args: Arguments) -> int:
272
+ exec_args = args.resolve()
273
+
274
+ ...
275
+ exit_code = _check_pre_commit_sync(exec.pre_commit, fmt)
276
+ ...
277
+ ```
278
+
279
+ Remove the `resolve_defaults()` call and the 6-tuple destructuring.
280
+
281
+ ### Step 7: Update `_merge_multiple_upstreams()`
282
+
283
+ **File**: `src/ruff_sync/core.py`
284
+
285
+ This function currently calls `resolve_defaults()` to get `branch`, `path`, and `exclude`. Since
286
+ these are already resolved by the time they reach `Arguments` (the CLI layer resolves them), the
287
+ call is actually redundant. However, if the function is also used by code that passes un-resolved
288
+ `Arguments`, we should change its signature to accept `ExecutionArgs` instead:
289
+
290
+ ```python
291
+ async def _merge_multiple_upstreams(
292
+ target_doc: TOMLDocument,
293
+ is_target_ruff_toml: bool,
294
+ args: ExecutionArgs, # ← changed from Arguments
295
+ client: httpx.AsyncClient,
296
+ ) -> TOMLDocument:
297
+ # No resolve_defaults() needed — args already has plain values
298
+ fetch_results = await fetch_upstreams_concurrently(
299
+ args.upstream, client, branch=args.branch, path=args.path
300
+ )
301
+ ...
302
+ ```
303
+
304
+ > [!NOTE]
305
+ > Look at call sites: `_merge_multiple_upstreams` is called from both `pull()` and `check()`.
306
+ > Both will now pass `exec` (the `ExecutionArgs`) instead of raw `args`.
307
+
308
+ ### Step 8: Remove `resolve_defaults` import from `core.py`
309
+
310
+ **File**: `src/ruff_sync/core.py`
311
+
312
+ After steps 5–7, `core.py` no longer needs `resolve_defaults` or `MISSING` from constants (unless
313
+ other code in the module still uses them). Clean up the imports.
314
+
315
+ ### Step 9: Clean up `ResolvedArgs`
316
+
317
+ **File**: `src/ruff_sync/cli.py`
318
+
319
+ The `ResolvedArgs` NamedTuple was expanded to include `validate: bool | MissingType`,
320
+ `strict: bool | MissingType`, and `pre_commit: bool | MissingType`. These should be reverted to
321
+ exclude the boolean flags. `ResolvedArgs` is only used inside `_resolve_args()` → `main()`, and
322
+ the boolean flags are already individually resolved by `_resolve_validate()` and `_resolve_strict()`.
323
+
324
+ Two options:
325
+
326
+ - **Option A (recommended)**: Remove `validate`, `strict`, `pre_commit` from `ResolvedArgs`.
327
+ Put them back onto `Arguments` directly in `main()` using the existing `_resolve_*` functions
328
+ (which already return `bool | MissingType`).
329
+
330
+ - **Option B**: Leave `ResolvedArgs` as-is but document that its purpose is to shuttle values
331
+ between `_resolve_args()` and `main()`.
332
+
333
+ With **Option A**, `main()` becomes:
334
+
335
+ ```python
336
+ resolved = _resolve_args(args, config, initial_to) # no validate/strict/pre_commit
337
+
338
+ exec_args = Arguments(
339
+ ...
340
+ validate=_resolve_validate(args, config),
341
+ strict=_resolve_strict(args, config),
342
+ pre_commit=_resolve_pre_commit(args, config),
343
+ ...
344
+ )
345
+ ```
346
+
347
+ ### Step 10: Update tests
348
+
349
+ **File**: `tests/test_constants.py`
350
+
351
+ Revert `resolve_defaults` tests to the original 3-tuple assertions:
352
+
353
+ ```python
354
+ def test_resolve_defaults_all_missing():
355
+ branch, path, exclude = resolve_defaults(MISSING, MISSING, MISSING)
356
+ assert branch == DEFAULT_BRANCH
357
+ assert path is None
358
+ assert exclude == DEFAULT_EXCLUDE
359
+ ```
360
+
361
+ Add **new** tests for `resolve_bool_flags()`:
362
+
363
+ ```python
364
+ from ruff_sync.constants import resolve_bool_flags
365
+
366
+ def test_resolve_bool_flags_all_missing():
367
+ validate, strict, pre_commit = resolve_bool_flags(MISSING, MISSING, MISSING)
368
+ assert validate is False
369
+ assert strict is False
370
+ assert pre_commit is True
371
+
372
+ def test_resolve_bool_flags_strict_implies_validate():
373
+ validate, strict, pre_commit = resolve_bool_flags(MISSING, True, MISSING)
374
+ assert validate is True
375
+ assert strict is True
376
+
377
+ def test_resolve_bool_flags_explicit_false():
378
+ validate, strict, pre_commit = resolve_bool_flags(False, False, False)
379
+ assert validate is False
380
+ assert strict is False
381
+ assert pre_commit is False
382
+ ```
383
+
384
+ **File**: `tests/test_serialization.py`
385
+
386
+ No changes needed — these tests construct `Arguments` with `MISSING` and pass to
387
+ `serialize_ruff_sync_config()`, which still consumes raw `Arguments`.
388
+
389
+ **File**: `tests/test_config_validation.py`
390
+
391
+ Update tests that currently destructure 6-tuples from `resolve_defaults()` to use the new API. Tests
392
+ for `pull()` and `check()` already pass `Arguments` objects, so they should work unchanged.
393
+
394
+ ### Step 11: Run full validation
395
+
396
+ ```bash
397
+ uv run ruff check . --fix
398
+ uv run ruff format .
399
+ uv run mypy .
400
+ uv run pytest -vv
401
+ ```
402
+
403
+ ---
404
+
405
+ ## Summary of Changes
406
+
407
+ | File | Change |
408
+ |------|--------|
409
+ | `src/ruff_sync/constants.py` | Revert `resolve_defaults` to 3-param/3-return. Add `resolve_bool_flags()`. |
410
+ | `src/ruff_sync/cli.py` | Add `ExecutionArgs` NamedTuple. Add `Arguments.resolve()` method. Clean up `ResolvedArgs`. |
411
+ | `src/ruff_sync/core.py` | Replace `resolve_defaults()` calls in `pull`/`check`/`_merge_multiple_upstreams` with `args.resolve()`. Remove `MISSING`/`resolve_defaults` imports. |
412
+ | `tests/test_constants.py` | Revert to 3-tuple tests. Add `resolve_bool_flags` tests. |
413
+ | `tests/test_config_validation.py` | Minor updates if any test directly calls `resolve_defaults` with 6 args. |
414
+
415
+ ### What does NOT change
416
+
417
+ - `Arguments` field types stay `bool | MissingType` for `validate`, `strict`, `pre_commit` (needed for serialization).
418
+ - `serialize_ruff_sync_config()` is unchanged — it still reads `MISSING` from `Arguments`.
419
+ - `_resolve_validate()`, `_resolve_strict()`, `_resolve_pre_commit()` in `cli.py` are unchanged.
420
+ - CLI argument parser is unchanged.
421
+
422
+ ### Architecture benefit
423
+
424
+ ```
425
+ Before (051ca28):
426
+ Arguments (MISSING everywhere) ──► resolve_defaults() called 3× in core.py
427
+
428
+ After (this refactor):
429
+ Arguments (MISSING for serialize) ──► .resolve() ──► ExecutionArgs (plain bools) ──► core logic
430
+ ↑ called ONCE
431
+ ```
432
+
433
+ The sentinel is contained to the serialization boundary. Execution logic gets clean types.
434
+
435
+ ---
436
+
437
+ ## Risks & Mitigations
438
+
439
+ | Risk | Mitigation |
440
+ |------|------------|
441
+ | Changing `_merge_multiple_upstreams` signature breaks internal callers | It's a private function with only 2 call sites (`pull`, `check`). Both are updated in the same PR. |
442
+ | `ExecutionArgs` adds another NamedTuple | It replaces the ad-hoc 6-tuple destructuring, which is worse. The NamedTuple is self-documenting and type-safe. |
443
+ | Tests that construct `Arguments` directly need `MISSING` for new defaults | Already the case — no change. |