ruff-sync 0.1.4.dev1__tar.gz → 0.1.5.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 (117) hide show
  1. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.agents/TESTING.md +2 -2
  2. ruff_sync-0.1.5.dev1/.agents/skills/dirty-equals/SKILL.md +37 -0
  3. ruff_sync-0.1.5.dev1/.agents/skills/dirty-equals/references/common-matchers.md +51 -0
  4. ruff_sync-0.1.5.dev1/.agents/skills/dirty-equals/references/toml-matching.md +54 -0
  5. ruff_sync-0.1.5.dev1/.agents/skills/dirty-equals/trigger_eval.json +30 -0
  6. ruff_sync-0.1.5.dev1/.agents/skills/mike/SKILL.md +64 -0
  7. ruff_sync-0.1.5.dev1/.agents/skills/mike/references/commands.md +78 -0
  8. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.agents/skills/ruff-sync-usage/SKILL.md +1 -1
  9. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.github/workflows/ci.yaml +61 -0
  10. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.pre-commit-config.yaml +1 -1
  11. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/AGENTS.md +31 -7
  12. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/CONTRIBUTING.md +1 -0
  13. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/PKG-INFO +1 -1
  14. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/gen_ref_pages.py +1 -1
  15. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/mkdocs.yml +8 -0
  16. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/pyproject.toml +5 -1
  17. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/src/ruff_sync/__init__.py +4 -0
  18. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/src/ruff_sync/cli.py +171 -81
  19. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/src/ruff_sync/constants.py +66 -4
  20. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/src/ruff_sync/core.py +20 -28
  21. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tasks.py +3 -3
  22. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/test_basic.py +20 -30
  23. ruff_sync-0.1.5.dev1/tests/test_ci_integration.py +34 -0
  24. ruff_sync-0.1.5.dev1/tests/test_ci_validation.py +226 -0
  25. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/test_config_validation.py +33 -0
  26. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/test_e2e.py +81 -2
  27. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/test_formatters.py +3 -3
  28. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/test_serialization.py +21 -38
  29. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/uv.lock +116 -56
  30. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.agents/formatters-architecture.md +0 -0
  31. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.agents/gitlab-reports.md +0 -0
  32. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.agents/issue-102-context.md +0 -0
  33. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.agents/skills/gh-issues/SKILL.md +0 -0
  34. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.agents/skills/mkdocs-generation/SKILL.md +0 -0
  35. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.agents/skills/mkdocs-generation/examples.md +0 -0
  36. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.agents/skills/mkdocs-generation/templates/api-reference.md +0 -0
  37. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.agents/skills/mkdocs-generation/templates/getting-started.md +0 -0
  38. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.agents/skills/mkdocs-generation/templates/index.md +0 -0
  39. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.agents/skills/mkdocs-generation/templates/mkdocs.yml +0 -0
  40. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.agents/skills/release-notes-generation/SKILL.md +0 -0
  41. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.agents/skills/ruff-sync-usage/references/ci-integration.md +0 -0
  42. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.agents/skills/ruff-sync-usage/references/configuration.md +0 -0
  43. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.agents/skills/ruff-sync-usage/references/troubleshooting.md +0 -0
  44. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.agents/workflows/add-test-case.md +0 -0
  45. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.git-blame-ignore-revs +0 -0
  46. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.github/dependabot.yml +0 -0
  47. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.github/workflows/complexity.yaml +0 -0
  48. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.github/workflows/docs.yaml +0 -0
  49. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.gitignore +0 -0
  50. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/.pre-commit-hooks.yaml +0 -0
  51. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/LICENSE.md +0 -0
  52. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/README.md +0 -0
  53. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/codecov.yml +0 -0
  54. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/configs/data-science-engineering/ruff.toml +0 -0
  55. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/configs/fastapi/ruff.toml +0 -0
  56. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/configs/kitchen-sink/ruff.toml +0 -0
  57. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/agent-skill.md +0 -0
  58. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/assets/favicon.png +0 -0
  59. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/assets/github-pr-annotation.png +0 -0
  60. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/assets/logo.png +0 -0
  61. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/assets/ruff_sync_banner.png +0 -0
  62. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/best-practices.md +0 -0
  63. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/ci-integration.md +0 -0
  64. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/configuration.md +0 -0
  65. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/contributing.md +0 -0
  66. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/examples/advanced-config.toml +0 -0
  67. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/examples/basic-config.toml +0 -0
  68. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/index.md +0 -0
  69. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/installation.md +0 -0
  70. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/pre-commit.md +0 -0
  71. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/pre-defined-configs.md +0 -0
  72. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/troubleshooting.md +0 -0
  73. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/url-resolution.md +0 -0
  74. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/docs/usage.md +0 -0
  75. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/scripts/check_dogfood.sh +0 -0
  76. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/scripts/gitclone_dogfood.sh +0 -0
  77. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/scripts/pull_dogfood.sh +0 -0
  78. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/skills-lock.json +0 -0
  79. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/src/ruff_sync/__main__.py +0 -0
  80. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/src/ruff_sync/formatters.py +0 -0
  81. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/src/ruff_sync/pre_commit.py +0 -0
  82. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/__init__.py +0 -0
  83. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/conftest.py +0 -0
  84. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/multi_upstream_final.toml +0 -0
  85. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/multi_upstream_initial.toml +0 -0
  86. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/multi_upstream_up1.toml +0 -0
  87. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/multi_upstream_up2.toml +0 -0
  88. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/no_changes_final.toml +0 -0
  89. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/no_changes_initial.toml +0 -0
  90. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/no_changes_upstream.toml +0 -0
  91. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/no_dotted_keys_final.toml +0 -0
  92. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/no_dotted_keys_initial.toml +0 -0
  93. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/no_dotted_keys_upstream.toml +0 -0
  94. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/no_ruff_cfg_final.toml +0 -0
  95. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/no_ruff_cfg_initial.toml +0 -0
  96. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/no_ruff_cfg_upstream.toml +0 -0
  97. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/readme_excludes_final.toml +0 -0
  98. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/readme_excludes_initial.toml +0 -0
  99. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/readme_excludes_upstream.toml +0 -0
  100. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/standard_final.toml +0 -0
  101. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/standard_initial.toml +0 -0
  102. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/lifecycle_tomls/standard_upstream.toml +0 -0
  103. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/ruff.toml +0 -0
  104. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/test_check.py +0 -0
  105. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/test_constants.py +0 -0
  106. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/test_corner_cases.py +0 -0
  107. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/test_deprecation.py +0 -0
  108. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/test_git_fetch.py +0 -0
  109. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/test_pre_commit.py +0 -0
  110. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/test_project.py +0 -0
  111. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/test_scaffold.py +0 -0
  112. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/test_toml_operations.py +0 -0
  113. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/test_url_handling.py +0 -0
  114. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/test_whitespace.py +0 -0
  115. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/w_ruff_sync_cfg/pyproject.toml +0 -0
  116. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/wo_ruff_cfg/pyproject.toml +0 -0
  117. {ruff_sync-0.1.4.dev1 → ruff_sync-0.1.5.dev1}/tests/wo_ruff_sync_cfg/pyproject.toml +0 -0
@@ -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,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
+ ]
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: mike
3
+ description: Use this skill to manage multiple versions of documentation for MkDocs-powered sites. Deploy new versions, update aliases (like 'latest' or 'stable'), set the default version for the site root, and manage versioned subdirectories in the deployment branch. Use this whenever the user wants to publish, version, or alias documentation, even if they don't explicitly mention "mike". Also use this when the user wants to troubleshoot or debug versioned documentation.
4
+ ---
5
+
6
+ # mike: MkDocs Versioning
7
+
8
+ `mike` is used in this project to manage a versioned documentation site, allowing side-by-side availability of `dev` (main branch) and stable release docs (e.g., `0.1.4`).
9
+
10
+ ## Prerequisites
11
+
12
+ `mike` is included in the `docs` dependency group.
13
+ ```bash
14
+ # Sync documentation dependencies
15
+ uv sync --group docs
16
+ ```
17
+
18
+ ## Project Strategy
19
+
20
+ This project follows a specific versioning strategy:
21
+ 1. **`dev`**: Represents the current `main` branch.
22
+ 2. **Stable Releases**: Versioned documentation (e.g., `0.1.4`) created upon release.
23
+ 3. **`stable`**: An alias always pointing to the most recent non-dev release.
24
+
25
+ ## Core Workflows
26
+
27
+ ### 1. Deploying Development Docs
28
+ Run this from the `main` branch to update the `dev` version:
29
+ ```bash
30
+ mike deploy dev --push --update-aliases
31
+ ```
32
+
33
+ ### 2. Deploying a Stable Release
34
+ When a new version is released (e.g., `0.1.4`), deploy it and update the `stable` alias:
35
+ ```bash
36
+ # Deploy the specific version and update 'stable'
37
+ mike deploy 0.1.4 stable --push --update-aliases
38
+
39
+ # Set 'stable' as the default version for the site root
40
+ mike set-default --push stable
41
+ ```
42
+
43
+ ## Reference Commands
44
+
45
+ | Action | Command |
46
+ | :--- | :--- |
47
+ | **Deploy** | `mike deploy <version> [alias]` |
48
+ | **List** | `mike list` |
49
+ | **Set Default** | `mike set-default <version>` |
50
+ | **Alias** | `mike alias <version> <alias>` |
51
+ | **Delete** | `mike delete <identifier>` |
52
+
53
+ ## CI/CD Integration
54
+
55
+ The deployment logic is automated in [.github/workflows/ci.yaml](.github/workflows/ci.yaml). It automatically:
56
+ - Extracts the version from `pyproject.toml`.
57
+ - Deploys to `dev` if the version contains `.dev`.
58
+ - Deploys to `<version>` and updates `stable` for official releases.
59
+
60
+ > [!IMPORTANT]
61
+ > **Do NOT** use `mike install-gh-pages`. It is deprecated and removed in the version used by this project. `mike deploy` handles branch initialization automatically.
62
+
63
+ > [!TIP]
64
+ > Use `mike serve` locally to preview the version switcher before pushing changes.
@@ -0,0 +1,78 @@
1
+ # Reference: mike CLI commands
2
+
3
+ Detailed parameter and core command reference for `mike`.
4
+
5
+ ## `deploy`
6
+
7
+ ```bash
8
+ mike deploy [options] <version> [alias]...
9
+ ```
10
+
11
+ Build the documentation for the specified version and commit it to the deployment branch.
12
+
13
+ - `<version>`: The version name to deploy (e.g., `1.0`, `v1.2.3`).
14
+ - `[alias]`: Optional aliases to point to this version (e.g., `latest`, `stable`).
15
+
16
+ **Common options:**
17
+ - `-p, --push`: Push the branch to the remote repository.
18
+ - `-b, --branch NAME`: Deployment branch (default: `gh-pages`).
19
+ - `-m, --message MESSAGE`: Custom commit message.
20
+ - `-u, --update-aliases`: Update existing aliases if they already point to a different version.
21
+ - `--alias-type {symlink,redirect,copy}`: Method for creating aliases.
22
+
23
+ ---
24
+
25
+ ## `alias`
26
+
27
+ ```bash
28
+ mike alias [options] <version> <alias>...
29
+ ```
30
+
31
+ Create or update aliases for a version without rebuilding the documentation.
32
+
33
+ ---
34
+
35
+ ## `set-default`
36
+
37
+ ```bash
38
+ mike set-default [options] <version>
39
+ ```
40
+
41
+ Set the default version for the documentation site's root redirect.
42
+
43
+ ---
44
+
45
+ ## `list`
46
+
47
+ ```bash
48
+ mike list [options]
49
+ ```
50
+
51
+ List all versions and aliases deployed to the deployment branch.
52
+
53
+ ---
54
+
55
+ ## `delete`
56
+
57
+ ```bash
58
+ mike delete [options] <identifier>...
59
+ ```
60
+
61
+ Delete one or more versions or aliases.
62
+
63
+ - `<identifier>`: Version or alias name.
64
+
65
+ **Options:**
66
+ - `--all`: Delete all versions/aliases.
67
+
68
+ ---
69
+
70
+ ## `serve`
71
+
72
+ ```bash
73
+ mike serve [options]
74
+ ```
75
+
76
+ Locally serve the documentation from the deployment branch. This is for testing the built versioned site.
77
+
78
+ - `-a, --addr ADDR`: Address to bind to (default: `localhost:8000`).
@@ -117,7 +117,7 @@ ruff-sync git@github.com:my-org/standards.git # SSH (shallow clone)
117
117
 
118
118
  | Flag | Meaning |
119
119
  |------|---------|
120
- | `--output-format` | `text` (default), `json`, `github` (PR annotations) |
120
+ | `--output-format` | `text` (default), `json`, `github`, `gitlab`, `sarif` (auto-detected in CI) |
121
121
  | `--semantic` | Ignore whitespace/comments in `check` |
122
122
  | `--pre-commit` | Sync `.pre-commit-config.yaml` hook version |
123
123
  | `--save` | Persist CLI args to `pyproject.toml` |
@@ -134,6 +134,67 @@ jobs:
134
134
  echo "Verifying that --semantic check fails for kitchen-sink config (expected failure)..."
135
135
  ! ruff-sync check --semantic https://github.com/Kilo59/ruff-sync --path configs/kitchen-sink
136
136
 
137
+ publish-docs:
138
+ name: Publish Documentation
139
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
140
+ needs: [pre-publish]
141
+ runs-on: ubuntu-latest
142
+ permissions:
143
+ contents: write
144
+ steps:
145
+ - name: Checkout
146
+ uses: actions/checkout@v4
147
+ with:
148
+ fetch-depth: 0
149
+
150
+ - name: Install uv
151
+ uses: astral-sh/setup-uv@v5
152
+
153
+ - name: Set up Python
154
+ run: uv python install 3.10
155
+
156
+ - name: Install dependencies
157
+ run: uv sync --group docs --frozen
158
+
159
+ - name: Deploy documentation
160
+ run: |
161
+ # Configure git
162
+ git config user.name "github-actions[bot]"
163
+ git config user.email "github-actions[bot]@users.noreply.github.com"
164
+
165
+ # mike deploy handles gh-pages initialization automatically
166
+
167
+ # Extract version from pyproject.toml in a TOML-aware way using already installed dependencies
168
+ VERSION=$(uv run python - << 'PY'
169
+ import pathlib
170
+ import tomlkit
171
+
172
+ pyproject = pathlib.Path("pyproject.toml")
173
+ data = tomlkit.parse(pyproject.read_text(encoding="utf-8"))
174
+
175
+ # Support both top-level `version` and `[project]` table `version`
176
+ version = (
177
+ data.get("project", {}).get("version")
178
+ or data.get("version")
179
+ )
180
+ if not version:
181
+ raise SystemExit("Version not found in pyproject.toml")
182
+ print(version)
183
+ PY
184
+ )
185
+ echo "Current Version: $VERSION"
186
+
187
+ if [[ "$VERSION" == *".dev"* ]]; then
188
+ echo "Deploying DEV documentation only..."
189
+ uv run mike deploy --push --update-aliases dev
190
+ else
191
+ echo "Deploying STABLE documentation..."
192
+ uv run mike deploy --push --update-aliases $VERSION stable
193
+ uv run mike set-default --push stable
194
+ # Also update dev to match stable on a full release
195
+ uv run mike deploy --push --update-aliases dev
196
+ fi
197
+
137
198
  publish:
138
199
  name: Build and publish to PyPI
139
200
  if: github.event_name == 'push' && github.ref == 'refs/heads/main'
@@ -16,7 +16,7 @@ repos:
16
16
  - id: no-commit-to-branch
17
17
  args: [--branch, develop, --branch, main]
18
18
  - repo: https://github.com/astral-sh/ruff-pre-commit
19
- rev: "v0.15.8"
19
+ rev: "v0.15.9"
20
20
  hooks:
21
21
  - id: ruff-check
22
22
  args: ["--fix"]
@@ -44,7 +44,7 @@ gh label list # See available labels
44
44
 
45
45
  - **Python** ≥ 3.10 (target version `py310`)
46
46
  - **Package Manager**: [uv](https://docs.astral.sh/uv/) — Use `uv run <command>` for all executions to ensure the correct environment.
47
- - **Linter / Formatter**: [Ruff](https://docs.astral.sh/ruff/) (`^0.15.0`)
47
+ - **Linter / Formatter**: [Ruff](https://docs.astral.sh/ruff/) (`>=0.15.0`)
48
48
  - **Type Checker**: [mypy](https://mypy-lang.org/) (strict mode)
49
49
  - **Test Framework**: [pytest](https://docs.pytest.org/) with `pytest-asyncio`, `respx`, `pyfakefs` (See [Testing Standards](.agents/TESTING.md))
50
50
  - **Coverage**: `coverage` + Codecov
@@ -67,18 +67,35 @@ gh label list # See available labels
67
67
  ci-integration.md
68
68
  src/ruff_sync/ # The application source
69
69
  __init__.py # Public API
70
- cli.py # CLI, merging logic, HTTP
71
- __main__.py # -m support
72
- tasks.py # Invoke tasks: lint, fmt, type-check, deps, new-case
70
+ __main__.py # CLI entry point (`python -m ruff_sync`)
71
+ cli.py # CLI argparse definition and orchestration
72
+ constants.py # Project-wide constants and default values
73
+ core.py # Core logic for merging, syncing, and repository handling
74
+ formatters.py # Logic for CLI output formatting (GitHub, GitLab, etc.)
75
+ pre_commit.py # Support for pre-commit hook generation and validation
76
+ tasks.py # Invoke tasks: lint, fmt, type-check, deps, new-case, release
73
77
  pyproject.toml # Project config, dependencies, ruff/mypy settings
74
78
  tests/
79
+ conftest.py # Shared pytest fixtures (mocking, temp dirs)
75
80
  ruff.toml # Test-specific ruff overrides (extends ../pyproject.toml)
76
81
  test_basic.py # Unit tests for core functions
82
+ test_check.py # Tests for --check mode and drift detection
83
+ test_ci_integration.py # CI-specific environment tests
84
+ test_ci_validation.py # Environment variable and CI output detection tests
85
+ test_config_validation.py # Validation of local configuration
86
+ test_constants.py # Tests for internal constants and sentinels
77
87
  test_corner_cases.py # Edge case tests for TOML merge logic
78
- test_whitespace.py # Tests for whitespace/comment preservation during merge
88
+ test_deprecation.py # Tests for handling of deprecated flags/settings
79
89
  test_e2e.py # End-to-end tests using lifecycle TOML fixtures
90
+ test_formatters.py # Serialization and formatting tests
91
+ test_git_fetch.py # Mocked git repository fetching tests
92
+ test_pre_commit.py # Pre-commit hook generation and sync tests
80
93
  test_project.py # Tests that validate project config consistency
81
- test_toml_operations.py # Tests for low-level TOML operations
94
+ test_scaffold.py # Tests for the new-case scaffold task
95
+ test_serialization.py # Tests for tomlkit serialization edge cases
96
+ test_toml_operations.py # Tests for low-level TOML operations
97
+ test_url_handling.py # Tests for GitHub and GitLab URL parsing
98
+ test_whitespace.py # Tests for whitespace/comment preservation during merge
82
99
  lifecycle_tomls/ # TOML fixture files (*_initial.toml, *_upstream.toml, *_final.toml)
83
100
  ```
84
101
 
@@ -113,7 +130,7 @@ Use this to make informed decisions rather than blindly suppressing rules.
113
130
  uv run ruff format .
114
131
  ```
115
132
 
116
- - Line length: **90** characters.
133
+ - Line length: **100** characters.
117
134
  - Docstring code formatting is enabled (`docstring-code-format = true`).
118
135
  - Preview formatting features are enabled.
119
136
 
@@ -207,6 +224,7 @@ Defined in `tasks.py`. **ALWAYS** run these through uv: `uv run invoke <task>`
207
224
  | `deps` | `sync` | Sync dependencies with uv |
208
225
  | `new-case` | `new-lifecycle-tomls` | Scaffold lifecycle TOML fixtures |
209
226
  | `docs` | | Build or serve documentation |
227
+ | `release` | | Tag and create a GitHub release |
210
228
 
211
229
  ## CI
212
230
 
@@ -223,3 +241,9 @@ CI is defined in `.github/workflows/ci.yaml`:
223
241
  3. **Pre-commit ruff version**: The ruff version in `.pre-commit-config.yaml` must stay in sync with the version in `pyproject.toml`. The test `test_pre_commit_versions_are_in_sync` enforces this.
224
242
  4. **Keep the ruff-sync-usage skill current**: After any change to CLI behavior (new flags, changed exit codes, new configuration keys, updated URL handling, etc.), update `.agents/skills/ruff-sync-usage/` accordingly. The `SKILL.md` covers quick start, workflows, exit codes, and gotchas. Detailed references live in `references/configuration.md`, `references/troubleshooting.md`, and `references/ci-integration.md`.
225
243
  5. **No `autouse=True` fixtures**: NEVER use `autouse=True` for pytest fixtures. All fixtures must be explicitly requested by the test functions that require them. This ensures dependencies are explicit and avoids hidden side effects.
244
+
245
+ ## Browser Tool Usage
246
+
247
+ - **Prefer `read_url_content`**: If you only need to extract text or markdown from a public URL, use `read_url_content`. It is faster and lighter.
248
+ - **Visual Interaction as Last Resort**: Only use `read_browser_page` or manual screen control when a page requires JavaScript execution, authentication, or complex visual interaction.
249
+ - **Task Specificity**: When using the browser subagent, provide highly specific tasks and clear exit criteria to minimize redundant interactions.
@@ -51,6 +51,7 @@ The project uses a `src` layout. All source code lives in `src/ruff_sync/`. Deve
51
51
  | `uv run invoke deps` | `sync` | Sync dependencies with uv |
52
52
  | `uv run invoke new-case` | `new-lifecycle-tomls` | Scaffold lifecycle TOML test fixtures |
53
53
  | `uv run invoke docs` | | Build or serve documentation |
54
+ | `uv run invoke release` | | Tag and create a GitHub release |
54
55
 
55
56
  ---
56
57
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ruff-sync
3
- Version: 0.1.4.dev1
3
+ Version: 0.1.5.dev1
4
4
  Summary: Synchronize Ruff linter configuration across projects
5
5
  Project-URL: Homepage, https://github.com/Kilo59/ruff-sync
6
6
  Project-URL: Documentation, https://kilo59.github.io/ruff-sync/
@@ -6,7 +6,7 @@ import pathlib
6
6
 
7
7
  import mkdocs_gen_files
8
8
 
9
- nav = mkdocs_gen_files.Nav() # type: ignore[attr-defined,no-untyped-call]
9
+ nav = mkdocs_gen_files.Nav()
10
10
 
11
11
  for path in sorted(pathlib.Path("src").rglob("*.py")):
12
12
  module_path = path.relative_to("src").with_suffix("")
@@ -25,6 +25,8 @@ theme:
25
25
  toggle:
26
26
  icon: material/brightness-4
27
27
  name: Switch to light mode
28
+ version:
29
+ method: mike
28
30
  features:
29
31
  - content.code.copy
30
32
  - content.tabs.link
@@ -39,6 +41,10 @@ theme:
39
41
  - search.share
40
42
  - search.suggest
41
43
  - toc.follow
44
+ - navigation.footer
45
+ font:
46
+ text: Inter
47
+ code: JetBrains Mono
42
48
  icon:
43
49
  repo: fontawesome/brands/github
44
50
 
@@ -87,6 +93,8 @@ markdown_extensions:
87
93
 
88
94
  plugins:
89
95
  - search
96
+ - mike:
97
+ alias_type: copy
90
98
  - gen-files:
91
99
  scripts:
92
100
  - docs/gen_ref_pages.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ruff-sync"
3
- version = "0.1.4.dev1"
3
+ version = "0.1.5.dev1"
4
4
  description = "Synchronize Ruff linter configuration across projects"
5
5
  keywords = ["ruff", "linter", "config", "synchronize", "python", "linting", "automation", "tomlkit", "pre-commit"]
6
6
  authors = [
@@ -42,6 +42,7 @@ ruff-sync = "ruff_sync:main"
42
42
  [dependency-groups]
43
43
  dev = [
44
44
  "coverage>=7.4.4",
45
+ "dirty-equals>=0.11",
45
46
  "invoke>=2.2.0",
46
47
  "mypy>=1.10.0",
47
48
  "packaging>=26.0",
@@ -61,6 +62,7 @@ docs = [
61
62
  "mkdocs-literate-nav>=0.6.2",
62
63
  "mkdocs-material>=9.7.5",
63
64
  "mkdocstrings[python]>=1.0.3",
65
+ "mike>=2.1.2",
64
66
  ]
65
67
 
66
68
  [tool.uv]
@@ -204,6 +206,7 @@ ignore = [
204
206
  "PLW1510", # subprocess check not needed in tests
205
207
  "PT013", # pytest import style, prevents importing param and or types in from style
206
208
  "TC002", # Move third-party import into type-checking block
209
+ "TID251", # allow cast in tests
207
210
  ]
208
211
 
209
212
  [tool.ruff.lint.pydocstyle]
@@ -220,6 +223,7 @@ docstring-code-line-length = "dynamic"
220
223
 
221
224
  [tool.ruff.lint.flake8-tidy-imports.banned-api]
222
225
  "unittest.mock".msg = "Prefer dedicated libraries to mock at external dependencies, such as `httpx` or `pyfakefs`."
226
+ "typing.cast".msg = "Please fix the underlying type issues instead of using typing.cast."
223
227
 
224
228
  [tool.ruff.lint.flake8-import-conventions]
225
229
  # Declare the banned `from` imports.
@@ -11,6 +11,7 @@ from .cli import (
11
11
  get_config,
12
12
  main,
13
13
  )
14
+ from .constants import OutputFormat
14
15
  from .core import (
15
16
  Config,
16
17
  FetchResult,
@@ -26,16 +27,19 @@ from .core import (
26
27
  to_git_url,
27
28
  toml_ruff_parse,
28
29
  )
30
+ from .formatters import get_formatter
29
31
 
30
32
  __all__ = [
31
33
  "Arguments",
32
34
  "Config",
33
35
  "FetchResult",
36
+ "OutputFormat",
34
37
  "RuffConfigFileName",
35
38
  "__version__",
36
39
  "check",
37
40
  "fetch_upstream_config",
38
41
  "get_config",
42
+ "get_formatter",
39
43
  "get_ruff_config",
40
44
  "get_ruff_tool_table",
41
45
  "is_ruff_toml_file",