modern-python-guidance 0.3.4__tar.gz → 0.3.6__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 (132) hide show
  1. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/.github/workflows/ci.yml +5 -2
  2. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/CHANGELOG.md +30 -0
  3. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/CONTRIBUTING.md +15 -3
  4. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/PKG-INFO +12 -11
  5. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/README.md +10 -10
  6. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/score_v5.py +6 -3
  7. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/pyproject.toml +18 -2
  8. modern_python_guidance-0.3.6/rules/modern-python.md +70 -0
  9. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/src/modern_python_guidance/__init__.py +1 -1
  10. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/src/modern_python_guidance/cli.py +30 -10
  11. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/src/modern_python_guidance/frontmatter.py +1 -3
  12. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/src/modern_python_guidance/mcp_server.py +8 -5
  13. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/src/modern_python_guidance/search.py +25 -17
  14. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/src/modern_python_guidance/setup_cmd.py +106 -2
  15. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/src/modern_python_guidance/uninstall_cmd.py +49 -2
  16. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/src/modern_python_guidance/version_detect.py +2 -6
  17. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/tests/test_cli_integration.py +27 -5
  18. modern_python_guidance-0.3.6/tests/test_guide_structure.py +180 -0
  19. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/tests/test_mcp_server.py +157 -70
  20. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/tests/test_retrieve.py +10 -2
  21. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/tests/test_search.py +6 -23
  22. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/tests/test_setup.py +205 -43
  23. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/tests/test_skill_sync.py +2 -6
  24. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/tests/test_uninstall.py +117 -35
  25. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/tests/test_version_detect.py +6 -18
  26. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/.github/workflows/check-python-release.yml +0 -0
  27. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/.github/workflows/publish.yml +0 -0
  28. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/.gitignore +0 -0
  29. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/LICENSE +0 -0
  30. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/LICENSE-MIT +0 -0
  31. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/SECURITY.md +0 -0
  32. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/edge-cases/opus48_multiline.py +0 -0
  33. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/edge-cases/valid_alt_patterns.py +0 -0
  34. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-a-modern/pyproject.toml +0 -0
  35. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-a-modern/src/app.py +0 -0
  36. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-a-modern/src/config.py +0 -0
  37. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-a-modern/src/crawler.py +0 -0
  38. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-a-modern/src/models.py +0 -0
  39. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-a-modern/src/scanner.py +0 -0
  40. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-a-modern/src/utils.py +0 -0
  41. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-a-outdated/pyproject.toml +0 -0
  42. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-a-outdated/setup.py +0 -0
  43. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-a-outdated/src/app.py +0 -0
  44. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-a-outdated/src/config.py +0 -0
  45. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-a-outdated/src/crawler.py +0 -0
  46. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-a-outdated/src/models.py +0 -0
  47. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-a-outdated/src/scanner.py +0 -0
  48. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-a-outdated/src/utils.py +0 -0
  49. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-b-modern/myapp/models.py +0 -0
  50. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-b-modern/myapp/views.py +0 -0
  51. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-b-outdated/myapp/models.py +0 -0
  52. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-b-outdated/myapp/views.py +0 -0
  53. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-c-modern/tests/test_calculator.py +0 -0
  54. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/fixtures/variant-c-outdated/tests/test_calculator.py +0 -0
  55. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/mcp-config.json +0 -0
  56. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/prompt-v2.txt +0 -0
  57. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/prompt-v3-mcp.txt +0 -0
  58. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/prompt-v3.txt +0 -0
  59. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/prompt-v4-a.txt +0 -0
  60. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/prompt-v4-b.txt +0 -0
  61. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/prompt-v4-c.txt +0 -0
  62. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/prompt.txt +0 -0
  63. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/prompts/v5-a-detailed.txt +0 -0
  64. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/prompts/v5-a-normal.txt +0 -0
  65. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/prompts/v5-a-terse.txt +0 -0
  66. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/prompts/v5-b-detailed.txt +0 -0
  67. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/prompts/v5-b-normal.txt +0 -0
  68. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/prompts/v5-b-terse.txt +0 -0
  69. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/prompts/v5-c-detailed.txt +0 -0
  70. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/prompts/v5-c-normal.txt +0 -0
  71. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/prompts/v5-c-terse.txt +0 -0
  72. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/run-mcp.sh +0 -0
  73. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/run-v4.sh +0 -0
  74. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/run-v5.sh +0 -0
  75. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/run.sh +0 -0
  76. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/score-v2.sh +0 -0
  77. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/score-v3.sh +0 -0
  78. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/score-v4.sh +0 -0
  79. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/score.sh +0 -0
  80. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/bench/test-scorer.sh +0 -0
  81. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/docs/benchmark-evaluation.md +0 -0
  82. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/docs/benchmark-procedure.md +0 -0
  83. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/docs/benchmark-v5.md +0 -0
  84. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/docs/design.md +0 -0
  85. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/SKILL.md +0 -0
  86. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/async/async-timeout-context.md +0 -0
  87. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/async/exception-groups.md +0 -0
  88. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/async/taskgroup-over-gather.md +0 -0
  89. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/data-structures/dataclass-modern.md +0 -0
  90. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/data-structures/dict-merge-operator.md +0 -0
  91. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/data-structures/match-case-patterns.md +0 -0
  92. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/django/django-async-views.md +0 -0
  93. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/django/django-check-constraints.md +0 -0
  94. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/django/django-json-field.md +0 -0
  95. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/fastapi/fastapi-annotated-depends.md +0 -0
  96. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/fastapi/fastapi-lifespan.md +0 -0
  97. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/fastapi/fastapi-typed-state.md +0 -0
  98. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/httpx/httpx-async-client-reuse.md +0 -0
  99. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/httpx/httpx-streaming.md +0 -0
  100. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-config.md +0 -0
  101. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-model-api.md +0 -0
  102. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-serialization.md +0 -0
  103. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-validators.md +0 -0
  104. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/pytest/pytest-parametrize.md +0 -0
  105. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/pytest/pytest-raises-match.md +0 -0
  106. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/pytest/pytest-tmp-path.md +0 -0
  107. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-2-style.md +0 -0
  108. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-async-session.md +0 -0
  109. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-mapped-column.md +0 -0
  110. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/stdlib/datetime-utc.md +0 -0
  111. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/stdlib/pathlib-over-os-path.md +0 -0
  112. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/stdlib/removeprefix-removesuffix.md +0 -0
  113. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/stdlib/template-strings.md +0 -0
  114. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/stdlib/tomllib-builtin.md +0 -0
  115. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/toolchain/no-pickle.md +0 -0
  116. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/toolchain/pyproject-toml-over-setup.md +0 -0
  117. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/toolchain/ruff-over-flake8.md +0 -0
  118. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/toolchain/safe-subprocess.md +0 -0
  119. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/toolchain/uv-over-pip.md +0 -0
  120. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/typing/deferred-annotations.md +0 -0
  121. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/typing/override-decorator.md +0 -0
  122. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/typing/paramspec-decorators.md +0 -0
  123. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/typing/type-parameter-syntax.md +0 -0
  124. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/typing/typeis-vs-typeguard.md +0 -0
  125. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/typing/union-syntax.md +0 -0
  126. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/skills/modern-python-guidance/guides/typing/use-builtin-generics.md +0 -0
  127. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/src/modern_python_guidance/__main__.py +0 -0
  128. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/src/modern_python_guidance/compat.py +0 -0
  129. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/src/modern_python_guidance/guide_index.py +0 -0
  130. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/src/modern_python_guidance/retrieve.py +0 -0
  131. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/tests/test_frontmatter.py +0 -0
  132. {modern_python_guidance-0.3.4 → modern_python_guidance-0.3.6}/tests/test_scorer_v5.py +0 -0
@@ -30,8 +30,11 @@ jobs:
30
30
  - name: Install dependencies
31
31
  run: uv pip install --system -e ".[dev]"
32
32
 
33
- - name: Run tests
34
- run: pytest --tb=short -q
33
+ - name: Check formatting
34
+ run: ruff format --check src/ tests/
35
35
 
36
36
  - name: Run linter
37
37
  run: ruff check src/ tests/
38
+
39
+ - name: Run tests
40
+ run: pytest --tb=short -q --cov --cov-report=term-missing
@@ -2,6 +2,36 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.3.6] — 2026-05-31
6
+
7
+ ### Added
8
+
9
+ - Rule-based delivery via symlink: `mpg setup` creates `.claude/rules/modern-python.md` that auto-injects modern Python guidance whenever Python-related files are touched, replacing reliance on probabilistic skill matching (closes #79)
10
+ - `setup_rules()` / `uninstall_rules()` mirroring skills symlink pattern
11
+ - `source.is_symlink()` security check to refuse symlink-to-symlink chains
12
+ - CI sync test enforcing SKILL.md body == rule body consistency
13
+ - 21 new tests (V-037 to V-060) for setup, uninstall, CI sync, and security
14
+
15
+ ### Changed
16
+
17
+ - `--skills-only` now includes Rules (both are project-local artifacts)
18
+ - README updated to document 4 delivery methods (was 3)
19
+ - `--project-dir` help text updated to mention Skills/Rules symlinks
20
+
21
+ ## [0.3.5] — 2026-05-30
22
+
23
+ ### Added
24
+
25
+ - CI format gate: `ruff format --check src/ tests/` runs before linter, catching formatting regressions at PR time (closes #19)
26
+ - Coverage reporting: `pytest-cov` with branch coverage and `fail_under = 59%` ratchet threshold (closes #15)
27
+ - Guide structure validation: 248 parametrized tests validating all 41 guides — frontmatter fields, section order, code fences, H1 title, no duplicate IDs (closes #16)
28
+ - CONTRIBUTING.md: documented CI checks, format fix command, and guide count update step
29
+
30
+ ### Changed
31
+
32
+ - Auto-formatted 12 existing source/test files with `ruff format` (whitespace only, no logic changes)
33
+ - CI step order: checkout → setup → install → **format check** → linter → tests (with `--cov`)
34
+
5
35
  ## [0.3.4] — 2026-05-30
6
36
 
7
37
  ### Fixed
@@ -37,13 +37,25 @@ See [docs/design.md](docs/design.md) for the full design document.
37
37
  | `frequency` | string | `high` (LLMs do this often), `medium`, `low` |
38
38
 
39
39
  3. Write BAD/GOOD/Why/Version Notes sections
40
- 4. Run `pytest` to verify the guide parses correctly
40
+ 4. Update `EXPECTED_GUIDE_COUNT` in `tests/test_guide_structure.py`
41
+ 5. Run `pytest` — the guide structure tests validate frontmatter, section order, and code fences automatically
41
42
 
42
43
  ## Running tests
43
44
 
44
45
  ```bash
45
46
  uv venv && source .venv/bin/activate
46
47
  uv pip install -e ".[dev]"
47
- pytest
48
- ruff check src/ tests/
48
+ pytest # 510+ tests including guide structure validation
49
+ ruff check src/ tests/ # lint
50
+ ruff format --check src/ tests/ # format check (CI enforced)
49
51
  ```
52
+
53
+ To auto-fix formatting: `ruff format src/ tests/`
54
+
55
+ ## CI checks
56
+
57
+ All PRs run these checks on Python 3.11, 3.12, and 3.13:
58
+
59
+ 1. `ruff format --check` — formatting
60
+ 2. `ruff check` — linting
61
+ 3. `pytest --cov` — tests with branch coverage (`fail_under = 59%`)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modern-python-guidance
3
- Version: 0.3.4
3
+ Version: 0.3.6
4
4
  Summary: Version-aware BAD/GOOD pattern guides that help AI coding agents generate modern Python
5
5
  Project-URL: Homepage, https://github.com/yottayoshida/modern-python-guidance
6
6
  Project-URL: Repository, https://github.com/yottayoshida/modern-python-guidance
@@ -25,6 +25,7 @@ Classifier: Typing :: Typed
25
25
  Requires-Python: >=3.11
26
26
  Requires-Dist: packaging>=23.0
27
27
  Provides-Extra: dev
28
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
28
29
  Requires-Dist: pytest>=7.0; extra == 'dev'
29
30
  Requires-Dist: ruff>=0.4.0; extra == 'dev'
30
31
  Description-Content-Type: text/markdown
@@ -43,7 +44,7 @@ Stop your AI from writing `typing.List`, `@validator`, and `setup.py`. 41 versio
43
44
  - **Measurable impact**: AI writes modern Python 98% of the time with mpg, vs 79% without — even with vague prompts (Opus 4.8, [V5 benchmark details](docs/benchmark-v5.md))
44
45
  - **41 guides** across stdlib, Pydantic, FastAPI, Django, SQLAlchemy, pytest, and toolchain
45
46
  - **Version-aware**: auto-detects your project's Python version and filters guides accordingly
46
- - **3 delivery methods**: MCP server, CLI, Agent Skills plugin
47
+ - **4 delivery methods**: MCP server, CLI, Agent Skills, and Rules (auto-injects on `.py` file touch)
47
48
  - **Not Ruff**: Ruff auto-fixes syntax (`List` → `list`). mpg guides design decisions that Ruff can't touch — `TaskGroup` over `gather`, Pydantic V2 migration, SQLAlchemy 2.0 style
48
49
 
49
50
  > **Note:** The tool itself requires Python 3.11+ to run. Guides cover patterns from Python 3.9 onward, and `--python-version` filters guides for your target environment.
@@ -57,7 +58,7 @@ pip install modern-python-guidance
57
58
  mpg setup
58
59
  ```
59
60
 
60
- This registers the MCP server and links Agent Skills in one command. Start a new Claude Code session afterwards — newly registered MCP servers and skills take effect on the next launch.
61
+ This registers the MCP server, links Agent Skills, and creates a Rules file (`.claude/rules/modern-python.md`) in one command. The Rules file auto-injects modern Python guidance whenever you touch Python-related files. Start a new Claude Code session afterwards — newly registered MCP servers, skills, and rules take effect on the next launch.
61
62
 
62
63
  ### CLI
63
64
 
@@ -89,7 +90,7 @@ claude mcp add mpg -- mpg mcp
89
90
  }
90
91
  ```
91
92
 
92
- **Agent Skills symlink (Claude Code):**
93
+ **Agent Skills + Rules only (Claude Code):**
93
94
  ```bash
94
95
  mpg setup --skills-only
95
96
  ```
@@ -98,25 +99,25 @@ mpg setup --skills-only
98
99
  | Flag | Purpose |
99
100
  |------|---------|
100
101
  | `--mcp-only` | MCP registration only |
101
- | `--skills-only` | Agent Skills symlink only |
102
+ | `--skills-only` | Project-local artifacts only (Skills + Rules) |
102
103
  | `--scope {user,local}` | MCP scope (default: user) |
103
- | `--project-dir PATH` | Target project for Skills symlink |
104
+ | `--project-dir PATH` | Target project for Skills/Rules symlinks |
104
105
  | `--dry-run` | Show what would be done |
105
106
 
106
- **Uninstall** — reverse `mpg setup` (deregister the MCP server and unlink Agent Skills):
107
+ **Uninstall** — reverse `mpg setup` (deregister the MCP server and unlink Agent Skills + Rules):
107
108
  ```bash
108
- mpg uninstall # remove both
109
+ mpg uninstall # remove all
109
110
  mpg uninstall --dry-run # preview what would be removed
110
111
  ```
111
112
 
112
113
  | Flag | Purpose |
113
114
  |------|---------|
114
115
  | `--mcp-only` | MCP deregistration only |
115
- | `--skills-only` | Agent Skills unlink only |
116
- | `--project-dir PATH` | Target project for the Skills symlink |
116
+ | `--skills-only` | Project-local artifacts only (Skills + Rules) |
117
+ | `--project-dir PATH` | Target project for Skills/Rules symlinks |
117
118
  | `--dry-run` | Show what would be done |
118
119
 
119
- `mpg uninstall` clears the MCP registration from every scope `setup` can write to (user and local), removes only the symlink mpg created (never its target or other skills), and is idempotent — running it on an already-clean state is a harmless no-op.
120
+ `mpg uninstall` clears the MCP registration from every scope `setup` can write to (user and local), removes only the symlinks mpg created (never their targets or other files), and is idempotent — running it on an already-clean state is a harmless no-op.
120
121
 
121
122
  </details>
122
123
 
@@ -12,7 +12,7 @@ Stop your AI from writing `typing.List`, `@validator`, and `setup.py`. 41 versio
12
12
  - **Measurable impact**: AI writes modern Python 98% of the time with mpg, vs 79% without — even with vague prompts (Opus 4.8, [V5 benchmark details](docs/benchmark-v5.md))
13
13
  - **41 guides** across stdlib, Pydantic, FastAPI, Django, SQLAlchemy, pytest, and toolchain
14
14
  - **Version-aware**: auto-detects your project's Python version and filters guides accordingly
15
- - **3 delivery methods**: MCP server, CLI, Agent Skills plugin
15
+ - **4 delivery methods**: MCP server, CLI, Agent Skills, and Rules (auto-injects on `.py` file touch)
16
16
  - **Not Ruff**: Ruff auto-fixes syntax (`List` → `list`). mpg guides design decisions that Ruff can't touch — `TaskGroup` over `gather`, Pydantic V2 migration, SQLAlchemy 2.0 style
17
17
 
18
18
  > **Note:** The tool itself requires Python 3.11+ to run. Guides cover patterns from Python 3.9 onward, and `--python-version` filters guides for your target environment.
@@ -26,7 +26,7 @@ pip install modern-python-guidance
26
26
  mpg setup
27
27
  ```
28
28
 
29
- This registers the MCP server and links Agent Skills in one command. Start a new Claude Code session afterwards — newly registered MCP servers and skills take effect on the next launch.
29
+ This registers the MCP server, links Agent Skills, and creates a Rules file (`.claude/rules/modern-python.md`) in one command. The Rules file auto-injects modern Python guidance whenever you touch Python-related files. Start a new Claude Code session afterwards — newly registered MCP servers, skills, and rules take effect on the next launch.
30
30
 
31
31
  ### CLI
32
32
 
@@ -58,7 +58,7 @@ claude mcp add mpg -- mpg mcp
58
58
  }
59
59
  ```
60
60
 
61
- **Agent Skills symlink (Claude Code):**
61
+ **Agent Skills + Rules only (Claude Code):**
62
62
  ```bash
63
63
  mpg setup --skills-only
64
64
  ```
@@ -67,25 +67,25 @@ mpg setup --skills-only
67
67
  | Flag | Purpose |
68
68
  |------|---------|
69
69
  | `--mcp-only` | MCP registration only |
70
- | `--skills-only` | Agent Skills symlink only |
70
+ | `--skills-only` | Project-local artifacts only (Skills + Rules) |
71
71
  | `--scope {user,local}` | MCP scope (default: user) |
72
- | `--project-dir PATH` | Target project for Skills symlink |
72
+ | `--project-dir PATH` | Target project for Skills/Rules symlinks |
73
73
  | `--dry-run` | Show what would be done |
74
74
 
75
- **Uninstall** — reverse `mpg setup` (deregister the MCP server and unlink Agent Skills):
75
+ **Uninstall** — reverse `mpg setup` (deregister the MCP server and unlink Agent Skills + Rules):
76
76
  ```bash
77
- mpg uninstall # remove both
77
+ mpg uninstall # remove all
78
78
  mpg uninstall --dry-run # preview what would be removed
79
79
  ```
80
80
 
81
81
  | Flag | Purpose |
82
82
  |------|---------|
83
83
  | `--mcp-only` | MCP deregistration only |
84
- | `--skills-only` | Agent Skills unlink only |
85
- | `--project-dir PATH` | Target project for the Skills symlink |
84
+ | `--skills-only` | Project-local artifacts only (Skills + Rules) |
85
+ | `--project-dir PATH` | Target project for Skills/Rules symlinks |
86
86
  | `--dry-run` | Show what would be done |
87
87
 
88
- `mpg uninstall` clears the MCP registration from every scope `setup` can write to (user and local), removes only the symlink mpg created (never its target or other skills), and is idempotent — running it on an already-clean state is a harmless no-op.
88
+ `mpg uninstall` clears the MCP registration from every scope `setup` can write to (user and local), removes only the symlinks mpg created (never their targets or other files), and is idempotent — running it on an already-clean state is a harmless no-op.
89
89
 
90
90
  </details>
91
91
 
@@ -1476,14 +1476,17 @@ def main() -> None:
1476
1476
  "c": "C (pytest)",
1477
1477
  }
1478
1478
  v_label = variant_labels.get(args.variant, args.variant)
1479
- print(f"=== V5 Scoring: Variant {v_label}, Run {args.run_id} ===")
1480
- print()
1479
+
1480
+ if args.output_format == "human":
1481
+ print(f"=== V5 Scoring: Variant {v_label}, Run {args.run_id} ===")
1482
+ print()
1481
1483
 
1482
1484
  output = {}
1483
1485
  for session_name in ("control", "treatment"):
1484
1486
  session_dir = results_dir / session_name
1485
1487
  if not session_dir.is_dir():
1486
- print(f" [{session_name}] Directory not found, skipping.")
1488
+ if args.output_format == "human":
1489
+ print(f" [{session_name}] Directory not found, skipping.")
1487
1490
  continue
1488
1491
  data = score_session(session_dir, args.variant)
1489
1492
  output[session_name] = data
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "modern-python-guidance"
7
- version = "0.3.4"
7
+ version = "0.3.6"
8
8
  description = "Version-aware BAD/GOOD pattern guides that help AI coding agents generate modern Python"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0 OR MIT"
@@ -34,6 +34,7 @@ dependencies = [
34
34
  [project.optional-dependencies]
35
35
  dev = [
36
36
  "pytest>=7.0",
37
+ "pytest-cov>=4.0",
37
38
  "ruff>=0.4.0",
38
39
  ]
39
40
 
@@ -48,11 +49,12 @@ Issues = "https://github.com/yottayoshida/modern-python-guidance/issues"
48
49
 
49
50
  [tool.hatch.build.targets.wheel]
50
51
  packages = ["src/modern_python_guidance"]
51
- only-include = ["src/modern_python_guidance", "skills"]
52
+ only-include = ["src/modern_python_guidance", "skills", "rules"]
52
53
 
53
54
  [tool.hatch.build.targets.wheel.sources]
54
55
  "src" = ""
55
56
  "skills" = "modern_python_guidance/skills"
57
+ "rules" = "modern_python_guidance/rules"
56
58
 
57
59
  [tool.ruff]
58
60
  target-version = "py311"
@@ -66,3 +68,17 @@ select = ["E", "F", "W", "I", "UP", "FURB", "B", "SIM", "RUF"]
66
68
 
67
69
  [tool.pytest.ini_options]
68
70
  testpaths = ["tests"]
71
+
72
+ [tool.coverage.run]
73
+ source = ["modern_python_guidance"]
74
+ branch = true
75
+
76
+ [tool.coverage.report]
77
+ show_missing = true
78
+ skip_empty = true
79
+ fail_under = 59
80
+ exclude_lines = [
81
+ "pragma: no cover",
82
+ "if __name__ == .__main__.",
83
+ "if TYPE_CHECKING:",
84
+ ]
@@ -0,0 +1,70 @@
1
+ ---
2
+ paths: ["**/*.py", "*.py", "**/pyproject.toml", "**/requirements*.txt", "**/setup.py", "**/setup.cfg", "**/.python-version", "**/Pipfile"]
3
+ ---
4
+
5
+ # Modern Python Guidance
6
+
7
+ Pre-generation design patterns that Ruff cannot auto-fix. Apply these transformations when writing or reviewing Python code.
8
+
9
+ ## When to use
10
+
11
+ - Writing new Python code (use modern patterns from the start)
12
+ - Reviewing Python code (flag outdated patterns)
13
+ - Migrating from Pydantic V1 to V2
14
+ - Upgrading Python version (check which new features are available)
15
+ - Replacing legacy tooling (setup.py, flake8, pip)
16
+
17
+ ## Embedded patterns (high-frequency, Ruff-uncovered)
18
+
19
+ ### Pydantic V2 (>=3.9)
20
+
21
+ - `@validator("f")` → `@field_validator("f")`
22
+ - `@root_validator` → `@model_validator(mode="after")`
23
+ - `class Config:` → `model_config = ConfigDict(...)`
24
+ - `orm_mode` → `from_attributes`, `allow_population_by_field_name` → `populate_by_name`
25
+ - `.parse_obj(d)` → `.model_validate(d)`, `.parse_raw(j)` → `.model_validate_json(j)`
26
+ - `.dict()` → `.model_dump()`, `.json()` → `.model_dump_json()`
27
+ - `.schema()` → `.model_json_schema()`, `.copy()` → `.model_copy()`
28
+
29
+ ### FastAPI (>=3.9)
30
+
31
+ - `@app.on_event("startup")`/`"shutdown"` → `@asynccontextmanager` lifespan + `FastAPI(lifespan=lifespan)`; yield dict becomes `request.state`
32
+ - `db: Session = Depends(get_db)` → `DbDep = Annotated[Session, Depends(get_db)]`; reusable type alias per PEP 593
33
+
34
+ ### httpx
35
+
36
+ - Per-request `async with httpx.AsyncClient()` → shared `AsyncClient` with `base_url`
37
+ - Caveat: shared client must be closed via `async with` or lifespan management
38
+
39
+ ### asyncio (>=3.11)
40
+
41
+ - `await asyncio.gather(a(), b())` → `async with asyncio.TaskGroup() as tg:` + `tg.create_task()`; access results via `task.result()`
42
+ - Caveat: 3.11+ only. `TaskGroup` cancels siblings on error and raises `ExceptionGroup`; `gather` preserves return order and supports `return_exceptions=True`
43
+
44
+ ### SQLAlchemy 2.0 (>=3.9)
45
+
46
+ - `session.query(User).filter()` → `session.execute(select(User).where())`; use `session.scalars()` for ORM results
47
+ - `Column(Integer)` → `Mapped[int] = mapped_column()`; type inferred from annotation, nullability from `Optional`/`| None`
48
+ - Sync `Session` with `asyncio.to_thread` → `AsyncSession` + `create_async_engine` + `async_sessionmaker`
49
+
50
+ ### Toolchain
51
+
52
+ - `setup.py` / `setup.cfg` → `pyproject.toml` with `[build-system]` + `[project]` (PEP 621)
53
+ - `subprocess.run(f"cmd {arg}", shell=True)` → `subprocess.run(["cmd", arg], check=True)`
54
+ - Caveat: `shell=True` is valid when pipes/globs are needed; use `shlex.quote()` for user input
55
+
56
+ ## All 41 guides by category
57
+
58
+ - **typing** (7): `use-builtin-generics`, `union-syntax`, `type-parameter-syntax`, `override-decorator`, `typeis-vs-typeguard`, `paramspec-decorators`, `deferred-annotations`
59
+ - **async** (3): `taskgroup-over-gather`, `exception-groups`, `async-timeout-context`
60
+ - **stdlib** (5): `datetime-utc`, `pathlib-over-os-path`, `tomllib-builtin`, `removeprefix-removesuffix`, `template-strings`
61
+ - **data-structures** (3): `dict-merge-operator`, `match-case-patterns`, `dataclass-modern`
62
+ - **pydantic** (4): `pydantic-v2-validators`, `pydantic-v2-config`, `pydantic-v2-model-api`, `pydantic-v2-serialization`
63
+ - **fastapi** (3): `fastapi-lifespan`, `fastapi-annotated-depends`, `fastapi-typed-state`
64
+ - **httpx** (2): `httpx-async-client-reuse`, `httpx-streaming`
65
+ - **django** (3): `django-json-field`, `django-async-views`, `django-check-constraints`
66
+ - **sqlalchemy** (3): `sqlalchemy-2-style`, `sqlalchemy-mapped-column`, `sqlalchemy-async-session`
67
+ - **pytest** (3): `pytest-parametrize`, `pytest-tmp-path`, `pytest-raises-match`
68
+ - **toolchain** (5): `pyproject-toml-over-setup`, `uv-over-pip`, `ruff-over-flake8`, `no-pickle`, `safe-subprocess`
69
+
70
+ For full code examples, use `mpg retrieve <guide-id>` or MCP tool `retrieve_guides`.
@@ -1,3 +1,3 @@
1
1
  """Modern Python Guidance — version-aware BAD/GOOD pattern guides for AI coding agents."""
2
2
 
3
- __version__ = "0.3.4"
3
+ __version__ = "0.3.6"
@@ -36,7 +36,9 @@ def main(argv: list[str] | None = None) -> None:
36
36
  p_search.add_argument("--category", help="Filter by category")
37
37
  p_search.add_argument("--limit", type=int, default=10, help="Max results (default: 10)")
38
38
  p_search.add_argument(
39
- "--format", choices=["json", "human"], default=None,
39
+ "--format",
40
+ choices=["json", "human"],
41
+ default=None,
40
42
  help="Output format (default: json when piped, human when TTY)",
41
43
  )
42
44
 
@@ -45,7 +47,9 @@ def main(argv: list[str] | None = None) -> None:
45
47
  p_retrieve.add_argument("ids", help="Comma-separated guide IDs")
46
48
  p_retrieve.add_argument("--python-version", help="Target Python version")
47
49
  p_retrieve.add_argument(
48
- "--format", choices=["json", "human"], default=None,
50
+ "--format",
51
+ choices=["json", "human"],
52
+ default=None,
49
53
  help="Output format (default: json when piped, human when TTY)",
50
54
  )
51
55
 
@@ -54,7 +58,9 @@ def main(argv: list[str] | None = None) -> None:
54
58
  p_list.add_argument("--category", help="Filter by category")
55
59
  p_list.add_argument("--python-version", help="Filter by Python version")
56
60
  p_list.add_argument(
57
- "--format", choices=["json", "human"], default=None,
61
+ "--format",
62
+ choices=["json", "human"],
63
+ default=None,
58
64
  help="Output format (default: json when piped, human when TTY)",
59
65
  )
60
66
 
@@ -67,27 +73,41 @@ def main(argv: list[str] | None = None) -> None:
67
73
 
68
74
  # setup
69
75
  p_setup = subparsers.add_parser(
70
- "setup", help="Register MCP server and link Agent Skills",
76
+ "setup",
77
+ help="Register MCP server and link Agent Skills + Rules",
71
78
  )
72
79
  p_setup.add_argument("--mcp-only", action="store_true", help="MCP registration only")
73
- p_setup.add_argument("--skills-only", action="store_true", help="Skills symlink only")
74
80
  p_setup.add_argument(
75
- "--scope", choices=["user", "local"], default="user",
81
+ "--skills-only", action="store_true", help="Project-local artifacts only (Skills + Rules)"
82
+ )
83
+ p_setup.add_argument(
84
+ "--scope",
85
+ choices=["user", "local"],
86
+ default="user",
76
87
  help="MCP scope (default: user)",
77
88
  )
78
89
  p_setup.add_argument(
79
- "--project-dir", type=Path, help="Project directory for Skills symlink",
90
+ "--project-dir",
91
+ type=Path,
92
+ help="Project directory for Skills/Rules symlinks",
80
93
  )
81
94
  p_setup.add_argument("--dry-run", action="store_true", help="Show what would be done")
82
95
 
83
96
  # uninstall
84
97
  p_uninstall = subparsers.add_parser(
85
- "uninstall", help="Reverse 'setup': deregister MCP server and unlink Agent Skills",
98
+ "uninstall",
99
+ help="Reverse 'setup': deregister MCP server and unlink Agent Skills + Rules",
86
100
  )
87
101
  p_uninstall.add_argument("--mcp-only", action="store_true", help="MCP deregistration only")
88
- p_uninstall.add_argument("--skills-only", action="store_true", help="Skills unlink only")
89
102
  p_uninstall.add_argument(
90
- "--project-dir", type=Path, help="Project directory for Skills symlink",
103
+ "--skills-only",
104
+ action="store_true",
105
+ help="Project-local artifacts only (Skills + Rules)",
106
+ )
107
+ p_uninstall.add_argument(
108
+ "--project-dir",
109
+ type=Path,
110
+ help="Project directory for Skills/Rules symlinks",
91
111
  )
92
112
  p_uninstall.add_argument("--dry-run", action="store_true", help="Show what would be done")
93
113
 
@@ -75,9 +75,7 @@ def _parse_raw(lines: list[str]) -> dict[str, Any]:
75
75
  if current_key is None:
76
76
  raise FrontmatterError("list item without preceding key", line=i)
77
77
  if not isinstance(data[current_key], list):
78
- raise FrontmatterError(
79
- f"list item for non-list key '{current_key}'", line=i
80
- )
78
+ raise FrontmatterError(f"list item for non-list key '{current_key}'", line=i)
81
79
  data[current_key].append(_parse_scalar(list_match.group(1).strip()))
82
80
  continue
83
81
 
@@ -340,11 +340,14 @@ def _handle_request(msg: dict) -> dict | None:
340
340
  return None
341
341
 
342
342
  if method == "initialize":
343
- result = _result_response(req_id, {
344
- "protocolVersion": PROTOCOL_VERSION,
345
- "capabilities": {"tools": {}},
346
- "serverInfo": {"name": "modern-python-guidance", "version": __version__},
347
- })
343
+ result = _result_response(
344
+ req_id,
345
+ {
346
+ "protocolVersion": PROTOCOL_VERSION,
347
+ "capabilities": {"tools": {}},
348
+ "serverInfo": {"name": "modern-python-guidance", "version": __version__},
349
+ },
350
+ )
348
351
  return None if is_notification else result
349
352
 
350
353
  if method == "tools/list":
@@ -60,19 +60,25 @@ def search(
60
60
 
61
61
  if score > 0:
62
62
  score += FREQ_BOOST.get(meta.frequency, 0.0)
63
- results.append(SearchResult(
64
- guide_id=guide_id,
65
- score=score,
66
- meta=meta,
67
- token_estimate=token_estimate(guide.body),
68
- snippet=guide.snippet,
69
- ))
63
+ results.append(
64
+ SearchResult(
65
+ guide_id=guide_id,
66
+ score=score,
67
+ meta=meta,
68
+ token_estimate=token_estimate(guide.body),
69
+ snippet=guide.snippet,
70
+ )
71
+ )
70
72
 
71
73
  results.sort(key=lambda r: (-r.score, r.guide_id))
72
74
 
73
75
  if not results:
74
76
  return _fuzzy_fallback(
75
- index, query, python_version=python_version, category=category, limit=limit,
77
+ index,
78
+ query,
79
+ python_version=python_version,
80
+ category=category,
81
+ limit=limit,
76
82
  )
77
83
 
78
84
  return results[:limit]
@@ -139,14 +145,16 @@ def _fuzzy_fallback(
139
145
  continue
140
146
  seen.add(guide_id)
141
147
  guide = candidates[guide_id]
142
- results.append(SearchResult(
143
- guide_id=guide_id,
144
- score=round(ratio, 3),
145
- meta=guide.meta,
146
- token_estimate=token_estimate(guide.body),
147
- fuzzy=True,
148
- snippet=guide.snippet,
149
- ))
148
+ results.append(
149
+ SearchResult(
150
+ guide_id=guide_id,
151
+ score=round(ratio, 3),
152
+ meta=guide.meta,
153
+ token_estimate=token_estimate(guide.body),
154
+ fuzzy=True,
155
+ snippet=guide.snippet,
156
+ )
157
+ )
150
158
 
151
159
  results.sort(key=lambda r: (-r.score, r.guide_id))
152
- return results[:min(limit, FUZZY_MAX)]
160
+ return results[: min(limit, FUZZY_MAX)]