ruff-sync 0.1.5.dev5__tar.gz → 0.1.6.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 (192) hide show
  1. ruff_sync-0.1.6.dev1/.agents/doc-fix.md +160 -0
  2. ruff_sync-0.1.6.dev1/.agents/plans/issue-100-roadmap-plan.md +370 -0
  3. ruff_sync-0.1.6.dev1/.agents/plans/issue-116-config-validation.md +741 -0
  4. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/mike/SKILL.md +104 -68
  5. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/ruff-sync-usage/SKILL.md +5 -0
  6. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/textual/SKILL.md +4 -0
  7. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/textual/references/testing.md +9 -2
  8. ruff_sync-0.1.6.dev1/.agents/skills/warnings-control/SKILL.md +108 -0
  9. ruff_sync-0.1.6.dev1/.agents/workflows/update-screenshots.md +52 -0
  10. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.github/workflows/ci.yaml +6 -9
  11. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/PKG-INFO +42 -13
  12. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/README.md +41 -12
  13. ruff_sync-0.1.6.dev1/docs/assets/inspect-main.png +0 -0
  14. ruff_sync-0.1.6.dev1/docs/assets/inspect-search.png +0 -0
  15. ruff_sync-0.1.6.dev1/docs/assets/screenshots/dashboard.svg +226 -0
  16. ruff_sync-0.1.6.dev1/docs/assets/screenshots/legend_help.svg +235 -0
  17. ruff_sync-0.1.6.dev1/docs/assets/screenshots/rule_details.svg +228 -0
  18. ruff_sync-0.1.6.dev1/docs/assets/screenshots/screenshot_sample.toml +53 -0
  19. ruff_sync-0.1.6.dev1/docs/assets/screenshots/search_omnibox.svg +221 -0
  20. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/gen_ref_pages.py +2 -1
  21. ruff_sync-0.1.6.dev1/docs/inspect.md +112 -0
  22. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/usage.md +32 -1
  23. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/mkdocs.yml +2 -1
  24. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/pyproject.toml +3 -1
  25. ruff_sync-0.1.6.dev1/scripts/generate_tui_screenshots.py +228 -0
  26. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/src/ruff_sync/__init__.py +2 -0
  27. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/src/ruff_sync/cli.py +25 -0
  28. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/src/ruff_sync/core.py +14 -0
  29. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/src/ruff_sync/tui/app.py +6 -2
  30. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/src/ruff_sync/tui/themes.py +1 -1
  31. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/src/ruff_sync/tui/widgets.py +28 -25
  32. ruff_sync-0.1.6.dev1/src/ruff_sync/validation.py +125 -0
  33. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tasks.py +12 -2
  34. ruff_sync-0.1.6.dev1/tests/test_config_validation.py +495 -0
  35. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/tui/test_tui.py +3 -2
  36. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/uv.lock +34 -4
  37. ruff_sync-0.1.5.dev5/.github/workflows/docs.yaml +0 -32
  38. ruff_sync-0.1.5.dev5/tests/test_config_validation.py +0 -125
  39. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/DEPENDENCIES.md +0 -0
  40. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/TESTING.md +0 -0
  41. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/decisions/0001-type-refactoring-strategy.md +0 -0
  42. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/decisions/0002-tui-node-ast.md +0 -0
  43. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/decisions/README.md +0 -0
  44. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/formatters-architecture.md +0 -0
  45. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/gitlab-reports.md +0 -0
  46. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/issue-102-context.md +0 -0
  47. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/ruff.toml +0 -0
  48. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/adr/SKILL.md +0 -0
  49. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/dirty-equals/SKILL.md +0 -0
  50. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/dirty-equals/references/common-matchers.md +0 -0
  51. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/dirty-equals/references/toml-matching.md +0 -0
  52. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/dirty-equals/trigger_eval.json +0 -0
  53. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/gh-issues/SKILL.md +0 -0
  54. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/mike/references/commands.md +0 -0
  55. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/mkdocs-generation/SKILL.md +0 -0
  56. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/mkdocs-generation/examples.md +0 -0
  57. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/mkdocs-generation/templates/api-reference.md +0 -0
  58. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/mkdocs-generation/templates/getting-started.md +0 -0
  59. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/mkdocs-generation/templates/index.md +0 -0
  60. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/mkdocs-generation/templates/mkdocs.yml +0 -0
  61. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/release-notes-generation/SKILL.md +0 -0
  62. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/ruff-sync-usage/references/ci-integration.md +0 -0
  63. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/ruff-sync-usage/references/configuration.md +0 -0
  64. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/ruff-sync-usage/references/troubleshooting.md +0 -0
  65. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/skill-creator/SKILL.md +0 -0
  66. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/skill-creator/references/best-practices.md +0 -0
  67. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/skill-creator/references/evaluating-skills.md +0 -0
  68. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/skill-creator/references/optimizing-descriptions.md +0 -0
  69. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/skill-creator/references/quickstart.md +0 -0
  70. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/skill-creator/references/sources.md +0 -0
  71. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/skill-creator/references/using-scripts.md +0 -0
  72. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/skill-creator/scripts/scaffold_skill.py +0 -0
  73. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/textual/examples/basic_app.py +0 -0
  74. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/textual/examples/reactive_example.py +0 -0
  75. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/textual/references/events.md +0 -0
  76. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/textual/references/styling.md +0 -0
  77. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/textual/references/widgets.md +0 -0
  78. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/type-checking/SKILL.md +0 -0
  79. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/type-checking/evals.json +0 -0
  80. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/type-checking/references/advanced-narrowing.md +0 -0
  81. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/type-checking/references/error-code-lookup.md +0 -0
  82. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/type-checking/references/generics.md +0 -0
  83. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/type-checking/references/naming.md +0 -0
  84. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/type-checking/references/protocol-patterns.md +0 -0
  85. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/type-checking/references/quickstart.md +0 -0
  86. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/type-checking/references/refactoring-patterns.md +0 -0
  87. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/skills/type-checking/scripts/audit_types.py +0 -0
  88. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/tui_design.md +0 -0
  89. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/tui_requirements.md +0 -0
  90. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/tui_rule_browsing.md +0 -0
  91. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/tui_rule_browsing_design.md +0 -0
  92. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.agents/workflows/add-test-case.md +0 -0
  93. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.git-blame-ignore-revs +0 -0
  94. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.github/dependabot.yml +0 -0
  95. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.github/workflows/complexity.yaml +0 -0
  96. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.gitignore +0 -0
  97. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.pre-commit-config.yaml +0 -0
  98. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/.pre-commit-hooks.yaml +0 -0
  99. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/AGENTS.md +0 -0
  100. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/CONTRIBUTING.md +0 -0
  101. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/LICENSE.md +0 -0
  102. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/codecov.yml +0 -0
  103. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/configs/data-science-engineering/ruff.toml +0 -0
  104. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/configs/fastapi/ruff.toml +0 -0
  105. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/configs/kitchen-sink/ruff.toml +0 -0
  106. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/agent-skill.md +0 -0
  107. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/assets/favicon.png +0 -0
  108. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/assets/github-job-summary.png +0 -0
  109. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/assets/github-pr-annotation.png +0 -0
  110. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/assets/logo.png +0 -0
  111. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/assets/ruff_sync_banner.png +0 -0
  112. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/best-practices.md +0 -0
  113. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/ci-integration.md +0 -0
  114. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/configuration.md +0 -0
  115. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/contributing.md +0 -0
  116. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/examples/advanced-config.toml +0 -0
  117. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/examples/basic-config.toml +0 -0
  118. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/index.md +0 -0
  119. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/installation.md +0 -0
  120. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/overrides/main.html +0 -0
  121. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/overrides/partials/version_warning.html +0 -0
  122. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/pre-commit.md +0 -0
  123. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/pre-defined-configs.md +0 -0
  124. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/troubleshooting.md +0 -0
  125. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/docs/url-resolution.md +0 -0
  126. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/scripts/check_dogfood.sh +0 -0
  127. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/scripts/gitclone_dogfood.sh +0 -0
  128. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/scripts/pull_dogfood.sh +0 -0
  129. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/skills-lock.json +0 -0
  130. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/src/ruff_sync/__main__.py +0 -0
  131. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/src/ruff_sync/config_io.py +0 -0
  132. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/src/ruff_sync/constants.py +0 -0
  133. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/src/ruff_sync/dependencies.py +0 -0
  134. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/src/ruff_sync/formatters.py +0 -0
  135. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/src/ruff_sync/pre_commit.py +0 -0
  136. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/src/ruff_sync/system.py +0 -0
  137. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/src/ruff_sync/tui/__init__.py +0 -0
  138. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/src/ruff_sync/tui/constants.py +0 -0
  139. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/src/ruff_sync/tui/screens.py +0 -0
  140. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/src/ruff_sync/tui/types_.py +0 -0
  141. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/src/ruff_sync/types_.py +0 -0
  142. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/__init__.py +0 -0
  143. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/conftest.py +0 -0
  144. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/multi_upstream_final.toml +0 -0
  145. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/multi_upstream_initial.toml +0 -0
  146. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/multi_upstream_up1.toml +0 -0
  147. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/multi_upstream_up2.toml +0 -0
  148. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/no_changes_final.toml +0 -0
  149. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/no_changes_initial.toml +0 -0
  150. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/no_changes_upstream.toml +0 -0
  151. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/no_dotted_keys_final.toml +0 -0
  152. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/no_dotted_keys_initial.toml +0 -0
  153. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/no_dotted_keys_upstream.toml +0 -0
  154. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/no_ruff_cfg_final.toml +0 -0
  155. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/no_ruff_cfg_initial.toml +0 -0
  156. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/no_ruff_cfg_upstream.toml +0 -0
  157. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/readme_excludes_final.toml +0 -0
  158. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/readme_excludes_initial.toml +0 -0
  159. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/readme_excludes_upstream.toml +0 -0
  160. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/standard_final.toml +0 -0
  161. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/standard_initial.toml +0 -0
  162. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/lifecycle_tomls/standard_upstream.toml +0 -0
  163. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/ruff.toml +0 -0
  164. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_basic.py +0 -0
  165. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_check.py +0 -0
  166. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_ci_integration.py +0 -0
  167. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_ci_validation.py +0 -0
  168. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_config_io.py +0 -0
  169. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_constants.py +0 -0
  170. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_corner_cases.py +0 -0
  171. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_dependencies.py +0 -0
  172. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_deprecation.py +0 -0
  173. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_e2e.py +0 -0
  174. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_formatters.py +0 -0
  175. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_git_fetch.py +0 -0
  176. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_minimal_imports.sh +0 -0
  177. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_pre_commit.py +0 -0
  178. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_project.py +0 -0
  179. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_rule_logic.py +0 -0
  180. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_scaffold.py +0 -0
  181. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_serialization.py +0 -0
  182. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_system.py +0 -0
  183. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_toml_operations.py +0 -0
  184. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_url_handling.py +0 -0
  185. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/test_whitespace.py +0 -0
  186. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/tui/__init__.py +0 -0
  187. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/tui/conftest.py +0 -0
  188. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/tui/test_themes.py +0 -0
  189. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/tui/test_tui_types.py +0 -0
  190. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/w_ruff_sync_cfg/pyproject.toml +0 -0
  191. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/wo_ruff_cfg/pyproject.toml +0 -0
  192. {ruff_sync-0.1.5.dev5 → ruff_sync-0.1.6.dev1}/tests/wo_ruff_sync_cfg/pyproject.toml +0 -0
@@ -0,0 +1,160 @@
1
+ # Fix Versioned Documentation CI (`mike` + MkDocs)
2
+
3
+ ## Summary
4
+
5
+ After researching the official `mike` README, Material for MkDocs docs, and comparing against real-world projects, the current setup has **one fatal flaw** and several secondary problems that have caused versioning to never work correctly.
6
+
7
+ ---
8
+
9
+ ## Root Cause: Two Workflows Fighting Each Other
10
+
11
+ The most critical issue is that **two separate workflows both deploy docs to `gh-pages` on every push to `main`**, and they use incompatible methods:
12
+
13
+ | Workflow | Trigger | Command | Effect |
14
+ |---|---|---|---|
15
+ | `ci.yaml` → `publish-docs` | push to `main` | `mike deploy --push dev` | Appends a `dev/` directory and `versions.json` to `gh-pages` |
16
+ | `docs.yaml` → `deploy` | push to `main` | `mkdocs gh-deploy --force` | **Completely overwrites the entire `gh-pages` branch** |
17
+
18
+ `mkdocs gh-deploy --force` does not know about `mike`. It builds a flat site and force-pushes it as the **sole content** of `gh-pages`. This destroys everything `mike` deployed, including `versions.json`, the `dev/` subdirectory, and the root redirect.
19
+
20
+ **This is why versioning has never worked: `docs.yaml` immediately destroys `mike`'s work.**
21
+
22
+ ---
23
+
24
+ ## Secondary Issues
25
+
26
+ ### 1. Wrong `alias_type` in `mkdocs.yml`
27
+ - `alias_type: copy` is set, but for GitHub Pages `redirect` is the most reliable option. `copy` creates full file copies for every alias, meaning the `stable` alias contains a complete separate copy of docs that becomes stale when the `stable` alias moves. `symlink` is the default and works on most servers, but GitHub Pages does not follow actual symlinks. `redirect` creates a thin HTML redirect per page at the alias path — the correct approach for GitHub Pages.
28
+
29
+ ### 2. Invalid `mike alias --push stable stable` bootstrap
30
+ - The CI attempts `mike alias --push stable stable` to "ensure `stable` exists." This is nonsensical — it tries to create an alias called `stable` pointing to `stable` (which doesn't exist yet). Mike will error, and the `|| true` suppresses it. It achieves nothing.
31
+
32
+ ### 3. `mike deploy --update-aliases dev` on stable releases
33
+ - When releasing `0.1.4 stable`, the CI also runs `mike deploy --push --update-aliases dev`. This re-deploys the current commit as *both* the stable `0.1.4` and the `dev` version, which makes `dev` identical to `stable`. `dev` should only be updated on commits to `main` when the version is a dev pre-release.
34
+
35
+ ### 4. The `validate-docs-build` job is fine but could use `--no-directory-urls`
36
+ - `mkdocs build --strict` is a good validation step on PRs. No change needed.
37
+
38
+ ---
39
+
40
+ ## Proposed Changes
41
+
42
+ ### [`DELETE`] `.github/workflows/docs.yaml`
43
+
44
+ This file is the root cause. It must be deleted. All documentation deployment must happen exclusively through `mike` in `ci.yaml`.
45
+
46
+ ---
47
+
48
+ ### [`MODIFY`] `.github/workflows/ci.yaml` — `publish-docs` job
49
+
50
+ **Fix the deploy logic:**
51
+
52
+ ```yaml
53
+ publish-docs:
54
+ name: Publish Documentation
55
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
56
+ needs: [pre-publish]
57
+ runs-on: ubuntu-latest
58
+ permissions:
59
+ contents: write
60
+ steps:
61
+ - name: Checkout
62
+ uses: actions/checkout@v4
63
+ with:
64
+ fetch-depth: 0
65
+
66
+ - name: Fetch gh-pages branch
67
+ # mike needs the gh-pages branch history to make incremental commits.
68
+ # Without this, mike may fail or corrupt the branch.
69
+ run: git fetch origin gh-pages --depth=1 || true
70
+
71
+ - name: Install uv
72
+ uses: astral-sh/setup-uv@v5
73
+
74
+ - name: Set up Python
75
+ run: uv python install 3.10
76
+
77
+ - name: Install dependencies
78
+ run: uv sync --group docs --frozen
79
+
80
+ - name: Configure git
81
+ run: |
82
+ git config user.name "github-actions[bot]"
83
+ git config user.email "github-actions[bot]@users.noreply.github.com"
84
+
85
+ - name: Extract version
86
+ id: version
87
+ run: |
88
+ VERSION=$(uv run python -c "
89
+ import pathlib, tomlkit
90
+ data = tomlkit.parse(pathlib.Path('pyproject.toml').read_text(encoding='utf-8'))
91
+ version = data.get('project', {}).get('version') or data.get('version')
92
+ if not version: raise SystemExit('Version not found')
93
+ print(version)
94
+ ")
95
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
96
+ echo "Current Version: $VERSION"
97
+
98
+ - name: Deploy dev documentation
99
+ if: contains(steps.version.outputs.version, '.dev')
100
+ run: uv run mike deploy --push --update-aliases dev
101
+
102
+ - name: Deploy stable documentation
103
+ if: "!contains(steps.version.outputs.version, '.dev')"
104
+ run: |
105
+ VERSION="${{ steps.version.outputs.version }}"
106
+ uv run mike deploy --push --update-aliases "$VERSION" stable
107
+ uv run mike set-default --push stable
108
+ ```
109
+
110
+ **Key changes from current:**
111
+ 1. Added `git fetch origin gh-pages --depth=1 || true` — required by mike to make incremental commits (per the mike README CI section).
112
+ 2. Split the version extraction into its own step with `id: version` so it can be used in `if` conditions.
113
+ 3. `dev` is **only** deployed when the version string contains `.dev`. The stable deploy no longer also writes a `dev` alias.
114
+ 4. Removed the bogus `mike alias --push stable stable || true` bootstrap.
115
+
116
+ ---
117
+
118
+ ### [`MODIFY`] `mkdocs.yml` — Fix `alias_type`
119
+
120
+ Change `alias_type` from `copy` to `redirect`:
121
+
122
+ ```yaml
123
+ plugins:
124
+ - mike:
125
+ alias_type: redirect # was: copy
126
+ canonical_version: stable
127
+ ```
128
+
129
+ `redirect` is the correct choice for GitHub Pages: it creates a lightweight `.html` redirect file at the alias path for every page (e.g., `stable/index.html` redirects to `0.1.4/index.html`). Unlike `symlink`, it works reliably on GitHub Pages. Unlike `copy`, it doesn't create diverging stale copies.
130
+
131
+ ---
132
+
133
+ ### [`MODIFY`] `.agents/skills/mike/SKILL.md`
134
+
135
+ - Remove the failed repair history section (no longer relevant once fixed)
136
+ - Update the CI workflow documentation to reflect the corrected single-workflow approach
137
+ - Document the critical `git fetch origin gh-pages --depth=1` requirement
138
+ - Document why `docs.yaml` must not exist alongside `mike deploy`
139
+ - Update `alias_type` guidance from `copy` to `redirect`
140
+
141
+ ---
142
+
143
+ ## Verification Plan
144
+
145
+ ### After merging:
146
+ 1. Check that `docs.yaml` is deleted and only `ci.yaml` deploys docs.
147
+ 2. Watch the next CI run on `main` — the `publish-docs` job should run `mike deploy --push --update-aliases dev`.
148
+ 3. Check the `gh-pages` branch: it should contain a `dev/` directory, a `versions.json`, and a root `index.html` redirect.
149
+ 4. Visit `https://kilo59.github.io/ruff-sync/` — it should redirect to `dev/` (since no stable version has been deployed yet).
150
+ 5. The version selector in the Material theme should appear showing `dev`.
151
+
152
+ ### For stable releases:
153
+ When version is bumped to a non-dev release and merged:
154
+ - `0.x.y/` directory appears on `gh-pages`
155
+ - `stable` alias points to `0.x.y/` via redirects
156
+ - Root redirects to `stable/`
157
+ - Version selector shows both `0.x.y [stable]` and `dev`
158
+
159
+ > [!IMPORTANT]
160
+ > After deploying, you may need to manually run a one-time `mike deploy --push dev` locally (or trigger the CI) to bootstrap the `gh-pages` branch if it currently contains a flat `mkdocs gh-deploy` dump. You may first want to run `mike delete --all --push` to wipe the broken state.
@@ -0,0 +1,370 @@
1
+ # Issue #100 — Feature Roadmap: Ecosystem-Inspired Improvements
2
+
3
+ > **Source issue**: [#100 Feature roadmap: ecosystem-inspired improvements for ruff-sync](https://github.com/Kilo59/ruff-sync/issues/100)
4
+ > **Status**: Planning / Pre-implementation
5
+
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ Issue #100 surveyed how 8+ ecosystems (ESLint, Prettier, Stylelint, RuboCop, golangci-lint, Cargo/Rust, pre-commit, and Ruff's own `extend`) handle shared linter/formatter config. The result is a **10-item prioritized roadmap** grouped into three tiers:
11
+
12
+ | Tier | Theme | Items |
13
+ |---|---|---|
14
+ | 🟢 Tier 1 — High Impact, Natural Extensions | Core capabilities that directly complement the existing merge pipeline | #1 Config Layering, #2 Richer CI drift output, #3 Lock/pin versions |
15
+ | 🟡 Tier 2 — Valuable Additions | Ergonomic improvements and power-user features | #4 Dry-run mode, #5 Per-key merge strategies, #6 `status` command, #7 Monorepo support |
16
+ | 🔵 Tier 3 — Forward-Looking / Exploratory | Long-lived R&D; high value but high uncertainty | #8 Bidirectional sync/push, #9 Webhook/GitHub App, #10 Config validation |
17
+
18
+ **Supplemental item**: README ecosystem documentation (adding RuboCop, Stylelint, golangci-lint, Cargo `[workspace.lints]`, pre-commit to the survey table).
19
+
20
+ ---
21
+
22
+ ## Current Architecture (Grounding)
23
+
24
+ Understanding the existing codebase is essential before proposing changes.
25
+
26
+ ### Key modules
27
+
28
+ | File | Role |
29
+ |---|---|
30
+ | `cli.py` | `ArgumentParser` wiring, `Arguments` (NamedTuple), resolution helpers (`_resolve_branch`, `_resolve_upstream`, …), `main()` |
31
+ | `core.py` | `fetch_upstream_config`, `merge_ruff_toml`, `_recursive_update`, `pull`, `check`, URL conversion |
32
+ | `constants.py` | `ConfKey` enum (TOML keys), `MissingType`/`MISSING` sentinel, `OutputFormat` enum, `resolve_defaults()` |
33
+ | `formatters.py` | `ResultFormatter` Protocol; `TextFormatter`, `GithubFormatter`, `JsonFormatter`, `GitlabFormatter`, `SarifFormatter` |
34
+ | `pre_commit.py` | Pre-commit hook version sync |
35
+ | `config_io.py` | `RuffConfigFileName`, `resolve_target_path`, `is_ruff_toml_file` |
36
+
37
+ ### Configuration contract
38
+
39
+ ```toml
40
+ [tool.ruff-sync]
41
+ upstream = "https://github.com/org/repo" # or list[str]
42
+ branch = "main"
43
+ path = ""
44
+ exclude = ["lint.per-file-ignores"]
45
+ to = "."
46
+ output-format = "github"
47
+ pre-commit-version-sync = true
48
+ ```
49
+
50
+ Config is read by `get_config()` (cached), mapped via `ConfKey.get_canonical()`, and resolved in `_resolve_args()`. The `MISSING` sentinel from `constants.py` is the standard way to distinguish "not provided" from explicit defaults.
51
+
52
+ ### CLI exit codes (current)
53
+
54
+ | Code | Meaning |
55
+ |---|---|
56
+ | `0` | Success |
57
+ | `1` | Dependency / config error |
58
+ | `4` | One or more upstream fetch failures |
59
+
60
+ ---
61
+
62
+ ## Tier 1 — High Impact, Natural Extensions
63
+
64
+ ### 1 — Config Inheritance / Layering (ESLint flat config model)
65
+
66
+ **Goal**: Allow an ordered list of upstream sources where later entries override earlier ones.
67
+
68
+ **Current state**: Already partially implemented — `upstream` already accepts `list[str]` and `fetch_upstreams_concurrently` fetches them concurrently, then `_merge_multiple_upstreams` merges sequentially. The fundamental machinery exists.
69
+
70
+ **Remaining gap**: The original issue's example shows *named* layers with comments, but the existing `upstream = [...]` syntax covers this already. The real user-visible gap is **documentation and discoverability** — users don't know this is possible.
71
+
72
+ **Implementation notes**:
73
+ - No new CLI flags needed. `upstream` already accepts multiple values both via CLI (`ruff-sync pull URL1 URL2`) and config (`upstream = ["URL1", "URL2"]`).
74
+ - Verify and add a focused test in `test_e2e.py` that a 3-layer merge resolves precedence correctly (last URL wins on a conflict).
75
+ - Update the README and the `ruff-sync-usage` skill with a "layered upstreams" example.
76
+
77
+ **Scope**: Small — mostly docs + a test.
78
+
79
+ ---
80
+
81
+ ### 2 — Richer CI Drift Output (exit codes + structured formats)
82
+
83
+ **Goal**: Differentiate exit codes so CI pipelines can distinguish classes of failures. Extend `--output-format` to cover SARIF for code-scanning UIs.
84
+
85
+ **Current state**:
86
+ - `OutputFormat` enum already has `TEXT`, `JSON`, `GITHUB`, `GITLAB`, `SARIF`. Classes `GithubFormatter`, `SarifFormatter`, `GitlabFormatter` all exist in `formatters.py`.
87
+ - Exit codes 0, 1, 4 are defined but there is **no dedicated code for "out of sync"** — `check` currently returns `1` for drift.
88
+
89
+ **Proposed exit code table**:
90
+
91
+ | Code | Meaning |
92
+ |---|---|
93
+ | `0` | In sync |
94
+ | `1` | Configuration / dependency error |
95
+ | `2` | Out of sync (drift detected) |
96
+ | `3` | Warning only (e.g. non-fatal upstream issue) |
97
+ | `4` | Upstream unreachable / fetch failure |
98
+
99
+ **Implementation notes**:
100
+ - Add `ExitCode` `IntEnum` to `constants.py` (e.g. `OK=0`, `CONFIG_ERROR=1`, `OUT_OF_SYNC=2`, `UPSTREAM_ERROR=4`).
101
+ - Update `core.check()` to return `ExitCode.OUT_OF_SYNC` (2) instead of `1` when drift is found.
102
+ - Update `main()` return values to use `ExitCode`.
103
+ - Update CI docs, `ruff-sync-usage` skill, and the `--output-format` help string.
104
+ - Tests: `test_check.py` will need updated expected exit code assertions.
105
+
106
+ > [!IMPORTANT]
107
+ > This is technically a **breaking change** for any CI script that checks `[ $? -eq 1 ]` for drift. Document the migration clearly in the changelog and consider a deprecation period where both `1` and `2` are valid for a release.
108
+
109
+ **Scope**: Medium — touches `constants.py`, `core.py`, `cli.py`, several tests.
110
+
111
+ ---
112
+
113
+ ### 3 — Lock / Pin Upstream Versions
114
+
115
+ **Goal**: Record the exact commit SHA fetched from an upstream so subsequent `check` runs can verify against the same snapshot.
116
+
117
+ **Inspiration**: `npm package-lock.json`, `go.sum`, pre-commit's `rev:` pinning.
118
+
119
+ **Proposed design**:
120
+
121
+ ```toml
122
+ # Written by `ruff-sync pull --lock` or automatically with `save = true`
123
+ [tool.ruff-sync.lock]
124
+ "https://github.com/my-org/standards" = "abc1234def5678" # SHA resolved at pull time
125
+ pulled-at = "2026-03-10T14:30:00Z"
126
+ ```
127
+
128
+ Or a standalone `ruff-sync.lock` file (simpler to gitignore independently).
129
+
130
+ **Implementation notes**:
131
+ - `fetch_upstream_config` returns a `FetchResult(buffer, resolved_upstream)`. Extend it with an optional `sha: str | None` field (add `sha` to the `NamedTuple`).
132
+ - For HTTP sources: resolve SHA via GitHub API (`/repos/{owner}/{repo}/commits?path=pyproject.toml&per_page=1`) if `GITHUB_TOKEN` is set; otherwise store the ETag.
133
+ - For git sources (`_fetch_via_git`): after `git clone --depth 1`, run `git rev-parse HEAD` to capture the exact SHA.
134
+ - Add `ruff-sync update` sub-command that bumps the lock to latest.
135
+ - `check` without a lock behaves as today; `check --locked` validates against the locked SHA.
136
+
137
+ > [!WARNING]
138
+ > HTTP ETag-based pinning is less reliable than SHA pinning. Make the limitation clear in docs. Recommend using git:// URLs for true reproducibility.
139
+
140
+ **Scope**: Large — new `FetchResult` field, new lock file I/O, new `update` subcommand, CI integration docs.
141
+
142
+ ---
143
+
144
+ ## Tier 2 — Valuable Additions
145
+
146
+ ### 4 — Dry-run / Preview Mode
147
+
148
+ **Goal**: `pull --dry-run` prints the fully merged TOML to stdout without writing it to disk.
149
+
150
+ **Current state**: `check --diff` shows a unified diff but doesn't print the full merged file. There is no `pull --dry-run`.
151
+
152
+ **Implementation notes**:
153
+ - Add `--dry-run` flag to the `pull` subparser in `cli.py`.
154
+ - In `core.pull()`, after computing `merged_doc`, if `args.dry_run` is `True`, write to stdout (`sys.stdout`) instead of the target file.
155
+ - The `Arguments` NamedTuple needs a `dry_run: bool = False` field.
156
+ - Add `ConfKey.DRY_RUN = "dry-run"` to `ConfKey` if it should be saveable to config (probably not — leave it CLI-only).
157
+
158
+ **Inspiration**: `terraform plan`, `npm pack --dry-run`.
159
+
160
+ **Scope**: Small-to-medium.
161
+
162
+ ---
163
+
164
+ ### 5 — Per-Key Merge Strategies
165
+
166
+ **Goal**: Support different merge behaviors per TOML key instead of the current "always overwrite" default.
167
+
168
+ **Proposed strategies**:
169
+
170
+ | Strategy | Behaviour |
171
+ |---|---|
172
+ | `replace` (default) | Upstream value completely replaces local value |
173
+ | `append` | For list values: append upstream items to local list (e.g. `lint.extend-select`) |
174
+ | `local-only` | Never touch this key (effectively an exclude per-key) |
175
+ | `local-first` | Use local value if present, otherwise use upstream |
176
+
177
+ **Config syntax proposal**:
178
+
179
+ ```toml
180
+ [tool.ruff-sync.merge-strategy]
181
+ "lint.extend-select" = "append"
182
+ "target-version" = "local-only"
183
+ ```
184
+
185
+ **Implementation notes**:
186
+ - Add `merge_strategy: dict[str, str]` to `Config` TypedDict.
187
+ - Add `ConfKey.MERGE_STRATEGY = "merge-strategy"` to `ConfKey`.
188
+ - Create `MergeStrategy` enum in `constants.py`: `REPLACE`, `APPEND`, `LOCAL_ONLY`, `LOCAL_FIRST`.
189
+ - Modify `_recursive_update` (in `core.py`) to accept an optional `merge_strategies: Mapping[str, MergeStrategy]` parameter. The dotted key path (e.g. `lint.extend-select`) is used to look up the strategy.
190
+ - The `exclude` mechanism could be unified with `local-only` as a migration convenience.
191
+
192
+ **Scope**: Medium-to-large — changes the core merge algorithm, new `ConfKey`, new tests in `test_corner_cases.py`.
193
+
194
+ ---
195
+
196
+ ### 6 — `status` Command — Richer Sync Reporting
197
+
198
+ **Goal**: A human-readable summary of the current sync state, including upstream SHA, last-pulled time, and per-tool drift summary.
199
+
200
+ **Proposed output**:
201
+
202
+ ```
203
+ ruff-sync v0.2.0
204
+ upstream: https://github.com/my-org/standards (main @ abc1234)
205
+ target: pyproject.toml
206
+ ruff ✅ in sync (last pulled: 2026-03-10)
207
+ mypy ❌ out of sync (3 keys differ)
208
+ ```
209
+
210
+ **Implementation notes**:
211
+ - `status` is a new subcommand in `cli.py` (beside `pull`, `check`, `inspect`).
212
+ - Requires the lock file (Feature #3) to report the "last pulled" timestamp and pinned SHA.
213
+ - For the drift summary: run the same diff logic as `check` but in summary-only mode (count of differing keys, not full diff text).
214
+ - If no lock exists, report "unknown" for last-pulled but still show drift status.
215
+
216
+ **Scope**: Medium — depends on #3 for full value, but the drift-count part can be built independently.
217
+
218
+ ---
219
+
220
+ ### 7 — Monorepo Support
221
+
222
+ **Goal**: Allow a single `pyproject.toml` at the repo root to configure independent sync targets for multiple sub-packages.
223
+
224
+ **Proposed config**:
225
+
226
+ ```toml
227
+ [[tool.ruff-sync.targets]]
228
+ to = "packages/api"
229
+ exclude = ["lint.per-file-ignores"]
230
+
231
+ [[tool.ruff-sync.targets]]
232
+ to = "packages/worker"
233
+ exclude = ["target-version"]
234
+ ```
235
+
236
+ **Implementation notes**:
237
+ - Add support for `[[tool.ruff-sync.targets]]` TOML array of tables.
238
+ - Each target entry inherits top-level `upstream`, `branch`, etc. unless overridden.
239
+ - `cli.py`'s `get_config()` and `_resolve_args()` would need to expand into a list of `Arguments` objects when targets are present.
240
+ - `pull` and `check` would iterate over all targets.
241
+ - This is a significant schema change — design carefully for backward compatibility.
242
+
243
+ > [!IMPORTANT]
244
+ > This is the largest Tier 2 item. Consider making it an ADR before implementation.
245
+
246
+ **Scope**: Large.
247
+
248
+ ---
249
+
250
+ ## Tier 3 — Forward-Looking / Exploratory
251
+
252
+ These are exploratory. Opening tracking issues is appropriate, but full implementation plans should come later.
253
+
254
+ ### 8 — Bidirectional Sync / Push Command
255
+
256
+ Push local changes upstream as a GitHub/GitLab PR. Very high value for teams making downstream discoveries.
257
+
258
+ **Implementation notes sketch**:
259
+ - Requires authenticated GitHub API access (`GITHUB_TOKEN`).
260
+ - Would create a branch on the upstream repo with the locally changed `[tool.ruff]` section, then open a PR.
261
+ - Scope: Very Large. Dependencies: git auth, PR API, fork/branch strategy.
262
+
263
+ ---
264
+
265
+ ### 9 — Webhook / GitHub App for Automatic PRs
266
+
267
+ A hosted service (like Renovate or Dependabot) that watches upstream config repos and opens PRs downstream when upstream changes.
268
+
269
+ **Notes**: This is out of scope for the `ruff-sync` CLI itself — it's a service/infrastructure concern. Could be built as a separate GitHub App or Action. The CLI's `check` exit codes (#2 above) are a prerequisite.
270
+
271
+ ---
272
+
273
+ ### 10 — Config Validation
274
+
275
+ Before applying a merge, validate the merged result:
276
+ - Warn if upstream references deprecated Ruff rules (query `ruff rule --all` or maintain a list).
277
+ - Warn if `target-version` conflicts with local `requires-python`.
278
+ - Optionally run `ruff check --no-fix` against the merged config to catch syntax errors.
279
+
280
+ **Implementation notes sketch**:
281
+ - Add a `validate` step inside `pull()` (between merge and write).
282
+ - Integrate with `ruff` subprocess (similar to how `pre_commit.py` calls subprocess tools).
283
+ - This overlaps with the `ruff-inspect` TUI — the TUI already shows per-rule status.
284
+
285
+ ---
286
+
287
+ ## Supplemental — README Ecosystem Documentation
288
+
289
+ Update the "How Other Ecosystems Solve This" README section to include:
290
+
291
+ | Tool | Mechanism | Notable feature |
292
+ |---|---|---|
293
+ | RuboCop (Ruby) | `inherit_from: <URL>` | Native remote URL — closest peer to ruff-sync |
294
+ | Stylelint (CSS) | `extends` via npm packages | Package-based distribution |
295
+ | golangci-lint (Go) | No native remote config | Teams use CI workarounds |
296
+ | Cargo/Rust | `[workspace.lints]` | Monorepo only, no remote |
297
+ | pre-commit | No `include`/`extends` | Repos must duplicate config |
298
+
299
+ ---
300
+
301
+ ## Recommended Sequencing
302
+
303
+ The items above are not all equal in effort or dependency. Here is a recommended order:
304
+
305
+ ```
306
+ Phase 0 (docs/quick wins):
307
+ - #1 Layered Upstreams — docs + test only
308
+ - README ecosystem table
309
+
310
+ Phase 1 (CI hardening):
311
+ - #2 Exit code differentiation + ExitCode enum
312
+
313
+ Phase 2 (ergonomics):
314
+ - #4 Dry-run mode (small, high user value)
315
+ - #6 Status command (lite version without lock)
316
+
317
+ Phase 3 (advanced features):
318
+ - #3 Lock file + `update` subcommand
319
+ - #6 Status command (full version with lock)
320
+ - #5 Per-key merge strategies
321
+
322
+ Phase 4 (monorepo):
323
+ - #7 Monorepo targets — design ADR first
324
+
325
+ Phase 5 (exploratory / long-term):
326
+ - #10 Config validation
327
+ - #8 Bidirectional push
328
+ - #9 Webhook/GitHub App (external service)
329
+ ```
330
+
331
+ ---
332
+
333
+ ## Open Questions / Design Decisions
334
+
335
+ > [!IMPORTANT]
336
+ > These need answers before starting Phase 2–3 work.
337
+
338
+ 1. **Lock file location**: `[tool.ruff-sync.lock]` (inside `pyproject.toml`) vs. a separate `ruff-sync.lock` file? A standalone file is easier to gitignore and diff-track, but adds a new file to every user's repo.
339
+
340
+ 2. **Exit code migration**: Should drift remain `1` for one more release cycle (with a deprecation warning) before moving to `2`? This minimizes breakage for existing CI pipelines.
341
+
342
+ 3. **`merge-strategy` syntax**: The dotted-key syntax (e.g. `"lint.extend-select" = "append"`) is readable but unusual for TOML. Alternative: an explicit array-of-tables with `key`, `strategy` fields. Which is more ergonomic?
343
+
344
+ 4. **`status` without a lock**: Should `status` fail loudly if no lock exists, or silently omit the "last pulled" field?
345
+
346
+ 5. **Monorepo schema backward compat**: The `[[tool.ruff-sync.targets]]` array-of-tables would coexist with top-level `upstream`, `to`, etc. What is the exact precedence rule? Should scalar `to` continue to work if `targets` is present?
347
+
348
+ ---
349
+
350
+ ## Testing Strategy (per feature)
351
+
352
+ | Feature | Primary test file(s) | Notes |
353
+ |---|---|---|
354
+ | #1 Layered upstreams | `test_e2e.py`, `test_corner_cases.py` | New lifecycle triple: base + domain → final |
355
+ | #2 Exit codes | `test_check.py`, `test_ci_integration.py` | Update expected return codes |
356
+ | #3 Lock file | New `test_lock.py` | `pyfakefs` for file I/O; `respx` for ETag mocking |
357
+ | #4 Dry-run | `test_basic.py`, `test_e2e.py` | Assert stdout contains merged TOML, file unchanged |
358
+ | #5 Merge strategies | `test_corner_cases.py` | `append`, `local-only`, `local-first` strategies |
359
+ | #6 Status | New `test_status.py` | Mocked upstream + lock file |
360
+ | #7 Monorepo | New `test_monorepo.py` | Multiple `pyfakefs` directories |
361
+ | #10 Config validation | New `test_validation.py` | Deprecated rule detection |
362
+
363
+ ---
364
+
365
+ ## Related Issues
366
+
367
+ - [#69](https://github.com/Kilo59/ruff-sync/issues/69) — Multi-tool config sync (overlaps with Tier 1 scope expansion)
368
+ - [#74](https://github.com/Kilo59/ruff-sync/issues/74) — Short CLI aliases (more useful if multi-tool lands)
369
+ - [#83](https://github.com/Kilo59/ruff-sync/issues/83) — Example Ruff configs for various domains (Tier 2)
370
+ - [#87](https://github.com/Kilo59/ruff-sync/issues/87) — Example config: Data Science / Data Engineering (Tier 2)