ruff-sync 0.1.4.dev0__tar.gz → 0.1.5__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 (190) hide show
  1. ruff_sync-0.1.5/.agents/DEPENDENCIES.md +47 -0
  2. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.agents/TESTING.md +2 -2
  3. ruff_sync-0.1.5/.agents/decisions/0001-type-refactoring-strategy.md +36 -0
  4. ruff_sync-0.1.5/.agents/decisions/0002-tui-node-ast.md +36 -0
  5. ruff_sync-0.1.5/.agents/decisions/README.md +11 -0
  6. ruff_sync-0.1.5/.agents/doc-fix.md +160 -0
  7. ruff_sync-0.1.5/.agents/ruff.toml +37 -0
  8. ruff_sync-0.1.5/.agents/skills/adr/SKILL.md +66 -0
  9. ruff_sync-0.1.5/.agents/skills/dirty-equals/SKILL.md +37 -0
  10. ruff_sync-0.1.5/.agents/skills/dirty-equals/references/common-matchers.md +51 -0
  11. ruff_sync-0.1.5/.agents/skills/dirty-equals/references/toml-matching.md +54 -0
  12. ruff_sync-0.1.5/.agents/skills/dirty-equals/trigger_eval.json +30 -0
  13. ruff_sync-0.1.5/.agents/skills/mike/SKILL.md +186 -0
  14. ruff_sync-0.1.5/.agents/skills/mike/references/commands.md +78 -0
  15. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.agents/skills/mkdocs-generation/templates/api-reference.md +1 -1
  16. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.agents/skills/mkdocs-generation/templates/mkdocs.yml +5 -2
  17. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.agents/skills/ruff-sync-usage/SKILL.md +1 -1
  18. ruff_sync-0.1.5/.agents/skills/ruff-sync-usage/references/ci-integration.md +222 -0
  19. ruff_sync-0.1.5/.agents/skills/skill-creator/SKILL.md +82 -0
  20. ruff_sync-0.1.5/.agents/skills/skill-creator/references/best-practices.md +56 -0
  21. ruff_sync-0.1.5/.agents/skills/skill-creator/references/evaluating-skills.md +49 -0
  22. ruff_sync-0.1.5/.agents/skills/skill-creator/references/optimizing-descriptions.md +29 -0
  23. ruff_sync-0.1.5/.agents/skills/skill-creator/references/quickstart.md +64 -0
  24. ruff_sync-0.1.5/.agents/skills/skill-creator/references/sources.md +18 -0
  25. ruff_sync-0.1.5/.agents/skills/skill-creator/references/using-scripts.md +59 -0
  26. ruff_sync-0.1.5/.agents/skills/skill-creator/scripts/scaffold_skill.py +185 -0
  27. ruff_sync-0.1.5/.agents/skills/textual/SKILL.md +57 -0
  28. ruff_sync-0.1.5/.agents/skills/textual/examples/basic_app.py +65 -0
  29. ruff_sync-0.1.5/.agents/skills/textual/examples/reactive_example.py +54 -0
  30. ruff_sync-0.1.5/.agents/skills/textual/references/events.md +84 -0
  31. ruff_sync-0.1.5/.agents/skills/textual/references/styling.md +54 -0
  32. ruff_sync-0.1.5/.agents/skills/textual/references/testing.md +65 -0
  33. ruff_sync-0.1.5/.agents/skills/textual/references/widgets.md +103 -0
  34. ruff_sync-0.1.5/.agents/skills/type-checking/SKILL.md +77 -0
  35. ruff_sync-0.1.5/.agents/skills/type-checking/evals.json +52 -0
  36. ruff_sync-0.1.5/.agents/skills/type-checking/references/advanced-narrowing.md +53 -0
  37. ruff_sync-0.1.5/.agents/skills/type-checking/references/error-code-lookup.md +69 -0
  38. ruff_sync-0.1.5/.agents/skills/type-checking/references/generics.md +60 -0
  39. ruff_sync-0.1.5/.agents/skills/type-checking/references/naming.md +56 -0
  40. ruff_sync-0.1.5/.agents/skills/type-checking/references/protocol-patterns.md +56 -0
  41. ruff_sync-0.1.5/.agents/skills/type-checking/references/quickstart.md +12 -0
  42. ruff_sync-0.1.5/.agents/skills/type-checking/references/refactoring-patterns.md +72 -0
  43. ruff_sync-0.1.5/.agents/skills/type-checking/scripts/audit_types.py +104 -0
  44. ruff_sync-0.1.5/.agents/tui_design.md +149 -0
  45. ruff_sync-0.1.5/.agents/tui_requirements.md +69 -0
  46. ruff_sync-0.1.5/.agents/tui_rule_browsing.md +44 -0
  47. ruff_sync-0.1.5/.agents/tui_rule_browsing_design.md +66 -0
  48. ruff_sync-0.1.5/.agents/workflows/update-screenshots.md +52 -0
  49. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.github/workflows/ci.yaml +81 -0
  50. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.pre-commit-config.yaml +2 -2
  51. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/AGENTS.md +44 -35
  52. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/CONTRIBUTING.md +1 -0
  53. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/PKG-INFO +21 -1
  54. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/README.md +18 -0
  55. ruff_sync-0.1.5/docs/assets/github-job-summary.png +0 -0
  56. ruff_sync-0.1.5/docs/assets/inspect-main.png +0 -0
  57. ruff_sync-0.1.5/docs/assets/inspect-search.png +0 -0
  58. ruff_sync-0.1.5/docs/assets/screenshots/dashboard.svg +226 -0
  59. ruff_sync-0.1.5/docs/assets/screenshots/legend_help.svg +235 -0
  60. ruff_sync-0.1.5/docs/assets/screenshots/rule_details.svg +228 -0
  61. ruff_sync-0.1.5/docs/assets/screenshots/screenshot_sample.toml +53 -0
  62. ruff_sync-0.1.5/docs/assets/screenshots/search_omnibox.svg +221 -0
  63. ruff_sync-0.1.5/docs/ci-integration.md +179 -0
  64. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/docs/gen_ref_pages.py +2 -1
  65. ruff_sync-0.1.5/docs/inspect.md +112 -0
  66. ruff_sync-0.1.5/docs/overrides/main.html +6 -0
  67. ruff_sync-0.1.5/docs/overrides/partials/version_warning.html +27 -0
  68. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/docs/usage.md +5 -3
  69. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/mkdocs.yml +12 -1
  70. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/pyproject.toml +13 -2
  71. ruff_sync-0.1.5/scripts/generate_tui_screenshots.py +228 -0
  72. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/src/ruff_sync/__init__.py +14 -2
  73. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/src/ruff_sync/cli.py +206 -80
  74. ruff_sync-0.1.5/src/ruff_sync/config_io.py +136 -0
  75. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/src/ruff_sync/constants.py +67 -4
  76. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/src/ruff_sync/core.py +169 -133
  77. ruff_sync-0.1.5/src/ruff_sync/dependencies.py +64 -0
  78. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/src/ruff_sync/formatters.py +356 -21
  79. ruff_sync-0.1.5/src/ruff_sync/system.py +178 -0
  80. ruff_sync-0.1.5/src/ruff_sync/tui/__init__.py +26 -0
  81. ruff_sync-0.1.5/src/ruff_sync/tui/app.py +267 -0
  82. ruff_sync-0.1.5/src/ruff_sync/tui/constants.py +9 -0
  83. ruff_sync-0.1.5/src/ruff_sync/tui/screens.py +187 -0
  84. ruff_sync-0.1.5/src/ruff_sync/tui/themes.py +22 -0
  85. ruff_sync-0.1.5/src/ruff_sync/tui/types_.py +263 -0
  86. ruff_sync-0.1.5/src/ruff_sync/tui/widgets.py +273 -0
  87. ruff_sync-0.1.5/src/ruff_sync/types_.py +31 -0
  88. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tasks.py +13 -3
  89. ruff_sync-0.1.5/tests/conftest.py +123 -0
  90. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/test_basic.py +22 -32
  91. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/test_check.py +208 -2
  92. ruff_sync-0.1.5/tests/test_ci_integration.py +34 -0
  93. ruff_sync-0.1.5/tests/test_ci_validation.py +226 -0
  94. ruff_sync-0.1.5/tests/test_config_io.py +153 -0
  95. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/test_config_validation.py +33 -0
  96. ruff_sync-0.1.5/tests/test_dependencies.py +59 -0
  97. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/test_e2e.py +81 -2
  98. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/test_formatters.py +238 -34
  99. ruff_sync-0.1.5/tests/test_minimal_imports.sh +54 -0
  100. ruff_sync-0.1.5/tests/test_rule_logic.py +102 -0
  101. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/test_serialization.py +21 -38
  102. ruff_sync-0.1.5/tests/test_system.py +54 -0
  103. ruff_sync-0.1.5/tests/tui/__init__.py +3 -0
  104. ruff_sync-0.1.5/tests/tui/conftest.py +22 -0
  105. ruff_sync-0.1.5/tests/tui/test_themes.py +36 -0
  106. ruff_sync-0.1.5/tests/tui/test_tui.py +381 -0
  107. ruff_sync-0.1.5/tests/tui/test_tui_types.py +87 -0
  108. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/uv.lock +247 -59
  109. ruff_sync-0.1.4.dev0/.agents/skills/ruff-sync-usage/references/ci-integration.md +0 -134
  110. ruff_sync-0.1.4.dev0/.github/workflows/docs.yaml +0 -32
  111. ruff_sync-0.1.4.dev0/docs/ci-integration.md +0 -103
  112. ruff_sync-0.1.4.dev0/tests/conftest.py +0 -64
  113. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.agents/formatters-architecture.md +0 -0
  114. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.agents/gitlab-reports.md +0 -0
  115. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.agents/issue-102-context.md +0 -0
  116. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.agents/skills/gh-issues/SKILL.md +0 -0
  117. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.agents/skills/mkdocs-generation/SKILL.md +0 -0
  118. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.agents/skills/mkdocs-generation/examples.md +0 -0
  119. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.agents/skills/mkdocs-generation/templates/getting-started.md +0 -0
  120. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.agents/skills/mkdocs-generation/templates/index.md +0 -0
  121. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.agents/skills/release-notes-generation/SKILL.md +0 -0
  122. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.agents/skills/ruff-sync-usage/references/configuration.md +0 -0
  123. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.agents/skills/ruff-sync-usage/references/troubleshooting.md +0 -0
  124. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.agents/workflows/add-test-case.md +0 -0
  125. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.git-blame-ignore-revs +0 -0
  126. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.github/dependabot.yml +0 -0
  127. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.github/workflows/complexity.yaml +0 -0
  128. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.gitignore +0 -0
  129. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/.pre-commit-hooks.yaml +0 -0
  130. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/LICENSE.md +0 -0
  131. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/codecov.yml +0 -0
  132. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/configs/data-science-engineering/ruff.toml +0 -0
  133. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/configs/fastapi/ruff.toml +0 -0
  134. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/configs/kitchen-sink/ruff.toml +0 -0
  135. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/docs/agent-skill.md +0 -0
  136. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/docs/assets/favicon.png +0 -0
  137. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/docs/assets/github-pr-annotation.png +0 -0
  138. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/docs/assets/logo.png +0 -0
  139. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/docs/assets/ruff_sync_banner.png +0 -0
  140. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/docs/best-practices.md +0 -0
  141. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/docs/configuration.md +0 -0
  142. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/docs/contributing.md +0 -0
  143. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/docs/examples/advanced-config.toml +0 -0
  144. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/docs/examples/basic-config.toml +0 -0
  145. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/docs/index.md +0 -0
  146. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/docs/installation.md +0 -0
  147. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/docs/pre-commit.md +0 -0
  148. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/docs/pre-defined-configs.md +0 -0
  149. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/docs/troubleshooting.md +0 -0
  150. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/docs/url-resolution.md +0 -0
  151. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/scripts/check_dogfood.sh +0 -0
  152. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/scripts/gitclone_dogfood.sh +0 -0
  153. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/scripts/pull_dogfood.sh +0 -0
  154. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/skills-lock.json +0 -0
  155. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/src/ruff_sync/__main__.py +0 -0
  156. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/src/ruff_sync/pre_commit.py +0 -0
  157. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/__init__.py +0 -0
  158. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/multi_upstream_final.toml +0 -0
  159. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/multi_upstream_initial.toml +0 -0
  160. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/multi_upstream_up1.toml +0 -0
  161. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/multi_upstream_up2.toml +0 -0
  162. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/no_changes_final.toml +0 -0
  163. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/no_changes_initial.toml +0 -0
  164. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/no_changes_upstream.toml +0 -0
  165. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/no_dotted_keys_final.toml +0 -0
  166. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/no_dotted_keys_initial.toml +0 -0
  167. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/no_dotted_keys_upstream.toml +0 -0
  168. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/no_ruff_cfg_final.toml +0 -0
  169. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/no_ruff_cfg_initial.toml +0 -0
  170. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/no_ruff_cfg_upstream.toml +0 -0
  171. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/readme_excludes_final.toml +0 -0
  172. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/readme_excludes_initial.toml +0 -0
  173. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/readme_excludes_upstream.toml +0 -0
  174. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/standard_final.toml +0 -0
  175. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/standard_initial.toml +0 -0
  176. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/lifecycle_tomls/standard_upstream.toml +0 -0
  177. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/ruff.toml +0 -0
  178. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/test_constants.py +0 -0
  179. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/test_corner_cases.py +0 -0
  180. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/test_deprecation.py +0 -0
  181. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/test_git_fetch.py +0 -0
  182. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/test_pre_commit.py +0 -0
  183. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/test_project.py +0 -0
  184. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/test_scaffold.py +0 -0
  185. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/test_toml_operations.py +0 -0
  186. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/test_url_handling.py +0 -0
  187. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/test_whitespace.py +0 -0
  188. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/w_ruff_sync_cfg/pyproject.toml +0 -0
  189. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/wo_ruff_cfg/pyproject.toml +0 -0
  190. {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.5}/tests/wo_ruff_sync_cfg/pyproject.toml +0 -0
@@ -0,0 +1,47 @@
1
+ # Optional Dependencies & Lazy Loading
2
+
3
+ To keep the `ruff-sync` CLI fast and lightweight, while still allowing for extensible features (like a TUI or specialized formatters), we use a standardized pattern for **Optional Dependencies**.
4
+
5
+ ## Requirements
6
+
7
+ 1. **Defensive Coding**: The application must never crash at startup if an optional dependency is missing. Instead, it should fail gracefully ONLY when the specific feature requiring that dependency is invoked.
8
+ 2. **Delayed Import Cycles**: We must avoid expensive imports of optional dependencies during the initial CLI application boot.
9
+ 3. **User-Friendly Errors**: If a dependency is missing, we must provide clear instructions on how the user can install it using `ruff-sync` extras.
10
+
11
+ ## Standard Pattern
12
+
13
+ All code that relies on an optional dependency MUST follow this pattern:
14
+
15
+ ### 1. Define the Extra in `pyproject.toml`
16
+
17
+ Add the dependency to the `[project.optional-dependencies]` section.
18
+
19
+ ```toml
20
+ [project.optional-dependencies]
21
+ tui = ["textual>=8.2.2"]
22
+ ```
23
+
24
+ ### 2. Check and Lazy-Import (Locally)
25
+
26
+ Never import optional dependencies at the top level of a module. All imports must happen inside the function or method that requires them, AFTER a defensive check.
27
+
28
+ ```python
29
+ def run_tui_feature():
30
+ # 1. First, check availability (fast, lightweight)
31
+ from ruff_sync.dependencies import require_dependency
32
+ require_dependency("textual", extra_name="tui")
33
+
34
+ # 2. Then, perform local import (delayed expensive cycle)
35
+ from textual.app import App
36
+ ...
37
+ ```
38
+
39
+ ## The Dependency Helper (`ruff_sync.dependencies`)
40
+
41
+ Use the utilities in `src/ruff_sync/dependencies.py` to handle these checks.
42
+
43
+ - `is_installed(package_name: str) -> bool`: A fast check using `importlib.util.find_spec` that doesn't trigger the package initialization.
44
+ - `require_dependency(package_name: str, extra_name: str) -> None`: Checks if a package is installed and raises a helpful `ImportError` if it is not.
45
+
46
+ ### Example ImportError
47
+ > "The 'textual' package is required for this feature. Install it with: pip install 'ruff-sync[tui]'"
@@ -8,7 +8,7 @@ This document defines the mandatory testing standards and patterns for the `ruff
8
8
  - **No Side Effects**: Tests must be isolated and not touch the actual filesystem or make real network calls.
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
- 2. **Semantic**: The actual data in the merged result matches the expected values.
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.
12
12
  - **DRY with Fixtures and Parameterization**: Avoid code duplication. Use fixtures for common setups and `@pytest.mark.parametrize` for matrix testing.
13
13
 
14
14
  ## 2. Tooling and Environment
@@ -130,6 +130,6 @@ def test_my_edge_case():
130
130
 
131
131
  ## 6. Code Coverage
132
132
 
133
- We target **high coverage** for `ruff_sync.py`.
133
+ We target **high coverage** for `src/ruff_sync/`.
134
134
  - Run coverage locally: `uv run coverage run -m pytest -vv && uv run coverage report`
135
135
  - New features MUST include unit tests in `tests/test_basic.py` or specialized files like `tests/test_whitespace.py` if they involve formatting logic.
@@ -0,0 +1,36 @@
1
+ # ADR 0001: Type Refactoring Strategy
2
+
3
+ ---
4
+ status: accepted
5
+ date: 2026-04-05
6
+ decider: Agent
7
+ ---
8
+
9
+ ## Context
10
+
11
+ The `ruff-sync` codebase, particularly the TUI and TOML merging logic, has historically relied on `dict[str, Any]` and deep `isinstance` checks. This makes the codebase difficult to maintain, prone to runtime errors, and provides poor IDE support.
12
+
13
+ Based on the establishing `type-checking` skill, we need a strategic approach to "Complexity Shifting" and "Healthy Abstraction".
14
+
15
+ ## Decision
16
+
17
+ We will implement a multi-phase type-refactoring strategy:
18
+
19
+ 1. **Eliminate unstructured `dict[str, Any]`**: Introduce `TypedDict` for core payloads (Ruff rules, linters) in the TUI and System modules.
20
+ 2. **Polymorphic TUI Widgets**: Replace recursive `isinstance(data, dict/list)` chains in widgets with structural polymorphism (e.g., `@singledispatchmethod` or a dedicated Node AST).
21
+ 3. **Strategic Complexity (High-ROI typing)**: Apply strict typing (`tomlkit` bounds) to core infrastructure like `core.py` (TOML merging) while keeping feature code simple.
22
+ 4. **Result Type Patterns**: Shift away from `Union[Success, Error]` with `isinstance` checks towards robust `Result` or tuple patterns.
23
+
24
+ ## Consequences
25
+
26
+ - **Pros**:
27
+ - Improved static analysis via `mypy`.
28
+ - Better developer experience (autocompletion, go-to-definition).
29
+ - Reduced runtime "surprises" in complex TOML merges.
30
+ - **Cons**:
31
+ - Initial "complexity tax" in core infrastructure types.
32
+ - Requires wrapping raw TOML data into structured nodes before rendering.
33
+
34
+ ## References
35
+
36
+ - [type-checking skill](../skills/type-checking/SKILL.md)
@@ -0,0 +1,36 @@
1
+ # ADR 0002: TUI Node AST Architecture
2
+
3
+ ---
4
+ status: accepted
5
+ date: 2026-04-05
6
+ decider: Agent
7
+ ---
8
+
9
+ ## Context
10
+
11
+ The TUI's configuration rendering (`widgets.py`) and selection logic (`app.py`) rely heavily on nested `isinstance` checks to navigate raw `dict`/`list` structures. This breaks down as the TOML structure becomes more complex (dotted keys, custom tables, list-of-tables).
12
+
13
+ We need a way to shift "rendering complexity" into "data structure complexity" using the polymorphism principle from ADR 0001.
14
+
15
+ ## Decision
16
+
17
+ We will implement a polymorphic **ConfigNode AST** for the TUI:
18
+
19
+ 1. **`ConfigNode` Protocol**: A baseline interface (`path()`, `key()`, `children()`, `doc_target()`).
20
+ 2. **Concrete Nodes**: `DictNode`, `ListNode`, `ScalarNode`, `LinterNode`, and `RulesCollectionNode`.
21
+ 3. **Recursive Wrapping**: A `wrap_data()` factory that converts raw TOML data into a `ConfigNode` tree during initial load.
22
+ 4. **UI-Layer Polymorphism**: Use `@singledispatchmethod` in widgets (e.g., `CategoryTable.render_node(node)`) to route rendering logic based on the node type, keeping Rich markup out of the model layer.
23
+
24
+ ## Consequences
25
+
26
+ - **Pros**:
27
+ - Centralized documentation routing (`doc_target()`).
28
+ - Widgets no longer need to know "what" they are rendering, only "how" to render a `ConfigNode`.
29
+ - Eliminates fragile nested type-inference logic.
30
+ - **Cons**:
31
+ - Requires an additional "wrapping" step during data load.
32
+ - Slightly more boilerplate in `src/ruff_sync/tui/types_.py`.
33
+
34
+ ## References
35
+
36
+ - [ADR 0001: Type Refactoring Strategy](./0001-type-refactoring-strategy.md)
@@ -0,0 +1,11 @@
1
+ # ADR Index
2
+
3
+ This is an internal record of architectural decisions for `ruff-sync`. These documents capture the historical "Why" for design choices made by both human and agentic developers.
4
+
5
+ | ID | Date | Status | Title |
6
+ | --- | --- | --- | --- |
7
+ | [0001](./0001-type-refactoring-strategy.md) | 2026-04-05 | Accepted | Type Refactoring Strategy |
8
+ | [0002](./0002-tui-node-ast.md) | 2026-04-05 | Accepted | TUI Node AST Architecture |
9
+
10
+ ---
11
+ *For instructions on how to create or manage ADRs, see the [ADR Skill](../skills/adr/SKILL.md).*
@@ -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,37 @@
1
+ # Agents-specific ruff linter overrides.
2
+ # This configuration applies to all files within the .agents/ directory.
3
+ #
4
+ # WHY AGENT SCRIPTS ARE TREATED DIFFERENTLY:
5
+ # ------------------------------------------
6
+ # 1. Faster iteration: Agent-related code (skills, scripts, and examples) is
7
+ # often experimental, short-lived, or used for automation.
8
+ # 2. Lower maintenance: We relax requirements like module-level docstrings
9
+ # to maintain speed and reduce boilerplate.
10
+ # 3. Local use: These scripts are usually run locally by the agent or developer
11
+ # rather than being part of the core production library.
12
+ # 4. Prototyping: Allowing assert statements (S101) makes it easier to write
13
+ # concise validation for specialized agent workflows.
14
+ # 5. Usage context for examples: We disable rules that move imports into
15
+ # if TYPE_CHECKING blocks because skill examples may be integrated into
16
+ # other applications in ways we cannot predict. Runtime access to
17
+ # imported objects is preserved to ensure compatibility with various
18
+ # application patterns (decorators, runtime reflection, etc).
19
+
20
+ # Inherit root linter settings from the project's pyproject.toml.
21
+ extend = "../pyproject.toml"
22
+
23
+ [lint]
24
+ # Specific overrides for agent python code, scripts, and skills.
25
+ extend-ignore = [
26
+ "D100", # missing-docstring-in-module - Agent scripts don't require module-level docstrings.
27
+ "D103", # missing-docstring-in-public-function - Agent functions don't require public docstrings.
28
+ "S101", # assert-statement - Asserts are preferred over other validation for internal agent scripts.
29
+
30
+ # Preserve runtime access for standalone examples and unknown integration:
31
+ # Disable rules that move imports into if TYPE_CHECKING: blocks.
32
+ "TC001", # Move third-party import into a type-checking block
33
+ "TC002", # Move third-party import into a type-checking block
34
+ "TC003", # Move second-party import into a type-checking block
35
+ "TC004", # Move first-party import into a type-checking block
36
+ "TC005", # Move standard library import into a type-checking block
37
+ ]
@@ -0,0 +1,66 @@
1
+ # ADR (Architectural Decision Record) Skill
2
+
3
+ ## Overview
4
+
5
+ Use this skill to capture, manage, and reference significant architectural decisions for `ruff-sync`. ADRs are stored internally in `.agents/decisions/` and provide a historical record of "Why" things are designed the way they are.
6
+
7
+ ## When to Create an ADR
8
+
9
+ Create an ADR when:
10
+ - A significant architectural change is proposed (e.g., new internal AST, changing the merging strategy).
11
+ - A long-running refactoring strategy is initiated (e.g., the Type-Checking Refactoring).
12
+ - A decision is made that will impact future development and needs to be "remembered" by future agent sessions.
13
+ - A "Implementation Plan" has been approved and reaches a level of architectural significance that warrants long-term persistence.
14
+
15
+ ## ADR Structure
16
+
17
+ Each ADR should be named `NNNN-slug.md` (e.g., `0001-type-refactoring-strategy.md`) and located in `.agents/decisions/`.
18
+
19
+ > [!IMPORTANT]
20
+ > **Use relative paths for ALL internal links.** Never use absolute paths (`file:///Users/...`) as they are machine-specific and break in different environments.
21
+
22
+ Use the following template:
23
+
24
+ ```markdown
25
+ # ADR [NNNN]: [Short Title]
26
+
27
+ ---
28
+ status: [proposed | accepted | superseded | deprecated]
29
+ date: YYYY-MM-DD
30
+ decider: [Agent | User]
31
+ ---
32
+
33
+ ## Context
34
+
35
+ What is the problem we are trying to solve? What are the constraints?
36
+
37
+ ## Decision
38
+
39
+ What are we doing? Be specific about the architectural shift.
40
+
41
+ ## Consequences
42
+
43
+ What are the trade-offs? What will be easier? What will be harder?
44
+
45
+ ## References
46
+
47
+ - Links to PRs or Issues (use `gh-issues` skill).
48
+ - Links to other ADRs (e.g., "Supersedes [NNNN]").
49
+ - Links to implementation plans or research.
50
+ ```
51
+
52
+ ## Workflows
53
+
54
+ ### 1. Creating a New ADR
55
+ 1. Identify the need for a persistent architectural record.
56
+ 2. Draft the ADR in `.agents/decisions/` using the next available number.
57
+ 3. Update `.agents/decisions/README.md` with the new entry.
58
+ 4. Reference the ADR in `AGENTS.md` if it represents a foundational shift.
59
+
60
+ ### 2. Superseding an ADR
61
+ 1. Create the new ADR (e.g., `0005`).
62
+ 2. Mark the old ADR (e.g., `0002`) as `status: superseded`.
63
+ 3. Add a link in the old ADR's `References` to the new one, and vice versa.
64
+
65
+ ### 3. Graduation
66
+ Implementation plans in `.agents/` are often temporary. When a plan is completed, if its decisions are architecturally significant, "graduate" it by distilling its core decisions into an ADR.
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: dirty-equals
3
+ description: Use this skill when you need to write declarative, readable, and maintainable assertions in Python tests. It is particularly effective for matching complex data structures, validating merged TOML configurations, and performing fuzzy matching on URLs, file paths, or object types. Use this when the user asks to "assert," "check," "verify," or "match" data in a test, even if they don't explicitly mention "dirty-equals."
4
+ ---
5
+
6
+ # `dirty-equals` Skill
7
+
8
+ This skill provides patterns and best practices for writing declarative assertions in the `ruff-sync` project using the `dirty-equals` library.
9
+
10
+ ## Overview
11
+
12
+ Instead of asserting on every field manually, compare against a "dirty" object that matches the expected structure and types.
13
+
14
+ ```python
15
+ from dirty_equals import IsInt, IsPartialDict, IsStr
16
+
17
+ def test_config_logic():
18
+ result = {"status": "active", "version": 1, "extra": "data"}
19
+ # Declarative assertion
20
+ assert result == IsPartialDict({
21
+ "status": IsStr(regex="act.*"),
22
+ "version": IsInt(gt=0),
23
+ })
24
+ ```
25
+
26
+ ## Detailed reference
27
+
28
+ Check these references for project-specific usage and common matchers:
29
+
30
+ - **[Common Matchers](references/common-matchers.md)**: Standard `dirty-equals` matchers like `IsPartialDict`, `IsInstance`, and more.
31
+ - **[Specialized Matching](references/toml-matching.md)**: Handling `tomlkit.unwrap()` and `Arguments._asdict()`.
32
+
33
+ ## Best Practices
34
+
35
+ - **Import Style**: Always use the `from dirty_equals import ...` style at the **module level** of your test files.
36
+ - **Semantic Matching**: Use `dirty-equals` for the semantic part of your test assertions, while using string comparisons or `respx` for structural/whitespace checks where appropriate.
37
+ - **Type Safety**: Prefer `IsInstance(httpx.URL)` or `IsInstance(pathlib.Path)` over custom regex for well-known types in the project.
@@ -0,0 +1,51 @@
1
+ # Common `dirty-equals` Matchers
2
+
3
+ This guide covers the most frequently used `dirty-equals` matchers in the `ruff-sync` project. Standard practice is to import these using the `from` syntax at the module level in test files.
4
+
5
+ ## Structural Matching
6
+
7
+ - **`IsPartialDict`**: Matches a subset of a dictionary. Essential for verifying specific fields in a larger configuration.
8
+ - **`IsDict`**: Matches an entire dictionary exactly (while still allowing fuzzy values).
9
+ - **`IsList`**: Matches a list, allowing fuzzy matching for elements.
10
+
11
+ ## Type and Instance Matching
12
+
13
+ - **`IsInstance(type)`**: Matches an object of a specific class. Used for objects like `httpx.URL` or `pathlib.Path`.
14
+ - **`IsStr()`**: Matches any string. Can also specify regex or prefix/suffix.
15
+ - **`IsInt()`**: Matches any integer.
16
+
17
+ ## Examples
18
+
19
+ ### Using `IsPartialDict` and `IsInstance`
20
+
21
+ ```python
22
+ from dirty_equals import IsInstance, IsPartialDict
23
+ import httpx
24
+ import pathlib
25
+
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
+ })
32
+ ```
33
+
34
+ ### String and Path Matching
35
+
36
+ ```python
37
+ from dirty_equals import IsInstance, IsStr
38
+ import pathlib
39
+
40
+ # Match a path instance
41
+ assert result_path == IsInstance(pathlib.Path)
42
+
43
+ # Match a string with a specific prefix
44
+ assert error_message == IsStr(regex="^ERROR:.*")
45
+ ```
46
+
47
+ ## Logic Matchers
48
+
49
+ - **`~` (Negation)**: Match values that are *not* the given value (e.g., `assert x == ~IsNone`).
50
+ - **`&` (AND)**: Combine matchers (e.g., `IsInt & IsPositive`).
51
+ - **`|` (OR)**: Combine alternatives.
@@ -0,0 +1,54 @@
1
+ # Specialized Matching for `ruff-sync`
2
+
3
+ This reference covers project-specific data structures where `dirty-equals` matchers require particular preparation for reliable results.
4
+
5
+ ## TOML Matching (`tomlkit`)
6
+
7
+ `tomlkit` returns proxy objects (containers and items). To reliably match these with `dirty-equals`, always use `.unwrap()` on the parsed document or table to convert it to plain Python types.
8
+
9
+ ### Correct Pattern
10
+
11
+ ```python
12
+ from dirty_equals import IsPartialDict
13
+ import tomlkit
14
+
15
+ # Parse some TOML
16
+ doc = tomlkit.parse('[tool.ruff]\nline-length = 80')
17
+
18
+ # Match the tool.ruff section
19
+ ruff_config = doc["tool"]["ruff"]
20
+
21
+ # Must use .unwrap()
22
+ assert ruff_config.unwrap() == IsPartialDict({"line-length": 80})
23
+ ```
24
+
25
+ ### Potential Gotcha
26
+ Direct matching of `tomlkit` proxy objects without `.unwrap()` can fail because `dirty-equals` may see the proxy's internal attributes rather than its data.
27
+
28
+ ## CLI `Arguments` matching
29
+
30
+ Our CLI arguments are defined as a `NamedTuple`. To match specific fields without validating the entire object, convert it to a dictionary using `._asdict()`.
31
+
32
+ ### Correct Pattern
33
+
34
+ ```python
35
+ from dirty_equals import IsInstance, IsPartialDict
36
+ import httpx
37
+ import pathlib
38
+ import ruff_sync_cli
39
+
40
+ # Sample Arguments instance
41
+ args = ruff_sync_cli.Arguments(
42
+ command="pull",
43
+ upstream=(httpx.URL("https://example.com"),),
44
+ to=pathlib.Path("."),
45
+ # ... other defaults ...
46
+ )
47
+
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
+ })
54
+ ```
@@ -0,0 +1,30 @@
1
+ [
2
+ {
3
+ "query": "I need to check if the tool.ruff section was merged correctly",
4
+ "should_trigger": true
5
+ },
6
+ {
7
+ "query": "add a unit test that verifies the output",
8
+ "should_trigger": true
9
+ },
10
+ {
11
+ "query": "where should I use dirty-equals",
12
+ "should_trigger": true
13
+ },
14
+ {
15
+ "query": "verify that this URL is from github.com",
16
+ "should_trigger": true
17
+ },
18
+ {
19
+ "query": "assert that the dictionary matches this schema",
20
+ "should_trigger": true
21
+ },
22
+ {
23
+ "query": "fix this syntax error in the CLI",
24
+ "should_trigger": false
25
+ },
26
+ {
27
+ "query": "run the tests with coverage",
28
+ "should_trigger": false
29
+ }
30
+ ]