modern-python-guidance 0.3.0__tar.gz → 0.3.2__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 (114) hide show
  1. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/CHANGELOG.md +53 -0
  2. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/CONTRIBUTING.md +1 -1
  3. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/PKG-INFO +46 -25
  4. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/README.md +45 -24
  5. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/docs/design.md +1 -1
  6. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/pyproject.toml +1 -1
  7. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/SKILL.md +3 -3
  8. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/fastapi/fastapi-lifespan.md +1 -1
  9. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/fastapi/fastapi-typed-state.md +5 -0
  10. modern_python_guidance-0.3.2/skills/modern-python-guidance/guides/stdlib/template-strings.md +70 -0
  11. modern_python_guidance-0.3.2/skills/modern-python-guidance/guides/typing/deferred-annotations.md +63 -0
  12. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/src/modern_python_guidance/__init__.py +1 -1
  13. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/src/modern_python_guidance/cli.py +55 -0
  14. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/src/modern_python_guidance/mcp_server.py +4 -4
  15. modern_python_guidance-0.3.2/src/modern_python_guidance/setup_cmd.py +189 -0
  16. modern_python_guidance-0.3.2/src/modern_python_guidance/uninstall_cmd.py +171 -0
  17. modern_python_guidance-0.3.2/tests/test_setup.py +425 -0
  18. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/tests/test_skill_sync.py +6 -6
  19. modern_python_guidance-0.3.2/tests/test_uninstall.py +371 -0
  20. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/.github/workflows/ci.yml +0 -0
  21. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/.github/workflows/publish.yml +0 -0
  22. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/.gitignore +0 -0
  23. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/LICENSE +0 -0
  24. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/LICENSE-MIT +0 -0
  25. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/SECURITY.md +0 -0
  26. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-a-modern/pyproject.toml +0 -0
  27. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-a-modern/src/app.py +0 -0
  28. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-a-modern/src/config.py +0 -0
  29. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-a-modern/src/crawler.py +0 -0
  30. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-a-modern/src/models.py +0 -0
  31. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-a-modern/src/scanner.py +0 -0
  32. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-a-modern/src/utils.py +0 -0
  33. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-a-outdated/pyproject.toml +0 -0
  34. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-a-outdated/setup.py +0 -0
  35. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-a-outdated/src/app.py +0 -0
  36. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-a-outdated/src/config.py +0 -0
  37. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-a-outdated/src/crawler.py +0 -0
  38. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-a-outdated/src/models.py +0 -0
  39. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-a-outdated/src/scanner.py +0 -0
  40. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-a-outdated/src/utils.py +0 -0
  41. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-b-modern/myapp/models.py +0 -0
  42. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-b-modern/myapp/views.py +0 -0
  43. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-b-outdated/myapp/models.py +0 -0
  44. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-b-outdated/myapp/views.py +0 -0
  45. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-c-modern/tests/test_calculator.py +0 -0
  46. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/fixtures/variant-c-outdated/tests/test_calculator.py +0 -0
  47. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/mcp-config.json +0 -0
  48. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/prompt-v2.txt +0 -0
  49. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/prompt-v3-mcp.txt +0 -0
  50. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/prompt-v3.txt +0 -0
  51. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/prompt-v4-a.txt +0 -0
  52. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/prompt-v4-b.txt +0 -0
  53. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/prompt-v4-c.txt +0 -0
  54. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/prompt.txt +0 -0
  55. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/run-mcp.sh +0 -0
  56. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/run-v4.sh +0 -0
  57. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/run.sh +0 -0
  58. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/score-v2.sh +0 -0
  59. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/score-v3.sh +0 -0
  60. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/score-v4.sh +0 -0
  61. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/score.sh +0 -0
  62. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/bench/test-scorer.sh +0 -0
  63. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/docs/benchmark-evaluation.md +0 -0
  64. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/docs/benchmark-procedure.md +0 -0
  65. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/async/async-timeout-context.md +0 -0
  66. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/async/exception-groups.md +0 -0
  67. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/async/taskgroup-over-gather.md +0 -0
  68. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/data-structures/dataclass-modern.md +0 -0
  69. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/data-structures/dict-merge-operator.md +0 -0
  70. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/data-structures/match-case-patterns.md +0 -0
  71. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/django/django-async-views.md +0 -0
  72. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/django/django-check-constraints.md +0 -0
  73. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/django/django-json-field.md +0 -0
  74. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/fastapi/fastapi-annotated-depends.md +0 -0
  75. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/httpx/httpx-async-client-reuse.md +0 -0
  76. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/httpx/httpx-streaming.md +0 -0
  77. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-config.md +0 -0
  78. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-model-api.md +0 -0
  79. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-serialization.md +0 -0
  80. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-validators.md +0 -0
  81. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/pytest/pytest-parametrize.md +0 -0
  82. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/pytest/pytest-raises-match.md +0 -0
  83. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/pytest/pytest-tmp-path.md +0 -0
  84. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-2-style.md +0 -0
  85. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-async-session.md +0 -0
  86. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-mapped-column.md +0 -0
  87. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/stdlib/datetime-utc.md +0 -0
  88. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/stdlib/pathlib-over-os-path.md +0 -0
  89. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/stdlib/removeprefix-removesuffix.md +0 -0
  90. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/stdlib/tomllib-builtin.md +0 -0
  91. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/toolchain/no-pickle.md +0 -0
  92. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/toolchain/pyproject-toml-over-setup.md +0 -0
  93. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/toolchain/ruff-over-flake8.md +0 -0
  94. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/toolchain/safe-subprocess.md +0 -0
  95. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/toolchain/uv-over-pip.md +0 -0
  96. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/typing/override-decorator.md +0 -0
  97. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/typing/paramspec-decorators.md +0 -0
  98. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/typing/type-parameter-syntax.md +0 -0
  99. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/typing/typeis-vs-typeguard.md +0 -0
  100. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/typing/union-syntax.md +0 -0
  101. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/skills/modern-python-guidance/guides/typing/use-builtin-generics.md +0 -0
  102. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/src/modern_python_guidance/__main__.py +0 -0
  103. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/src/modern_python_guidance/compat.py +0 -0
  104. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/src/modern_python_guidance/frontmatter.py +0 -0
  105. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/src/modern_python_guidance/guide_index.py +0 -0
  106. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/src/modern_python_guidance/retrieve.py +0 -0
  107. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/src/modern_python_guidance/search.py +0 -0
  108. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/src/modern_python_guidance/version_detect.py +0 -0
  109. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/tests/test_cli_integration.py +0 -0
  110. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/tests/test_frontmatter.py +0 -0
  111. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/tests/test_mcp_server.py +0 -0
  112. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/tests/test_retrieve.py +0 -0
  113. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/tests/test_search.py +0 -0
  114. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.2}/tests/test_version_detect.py +0 -0
@@ -2,8 +2,57 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.3.2] — 2026-05-29
6
+
7
+ ### Added
8
+
9
+ - `deferred-annotations` guide (PEP 649): drop unnecessary `from __future__ import annotations` on Python 3.14+ projects where annotations are lazily evaluated by default (closes #28)
10
+ - `template-strings` guide (PEP 750): use t-strings with processing functions for safe SQL/HTML parameterization instead of f-string interpolation (closes #28)
11
+ - Guide count: 39 → 41. Layer 1 coverage: 16 → 18
12
+
13
+ ### Fixed
14
+
15
+ - `setup_mcp` now catches `OSError` from `subprocess.run`, matching `uninstall_mcp` behavior — an unexecutable `claude` binary produces a clean error message instead of a traceback (closes #65)
16
+ - MCP `retrieve_guides` schema `maxItems` and runtime guard updated from 39 to 41 to allow retrieval of all guides
17
+
18
+ ## [0.3.1] — 2026-05-29
19
+
20
+ ### Added
21
+
22
+ - `mpg uninstall` command: reverses `mpg setup` by deregistering the MCP server and removing the Agent Skills symlink in one command (closes #63)
23
+ - CLI flags: `--mcp-only`, `--skills-only`, `--project-dir`, `--dry-run` (no `--scope`; uninstall clears every scope `setup` can write to)
24
+ - Per-scope MCP deregistration (`claude mcp remove -s local` and `-s user`): a live probe showed `claude mcp remove` without a scope removes nothing when the server is registered in multiple scopes, so uninstall enumerates scopes explicitly to avoid leaving residue
25
+ - Symlink-only removal safety: only the symlink mpg created is removed (never its target), a non-symlink entity at the link path is refused, dangling symlinks are removed, and the parent `.claude/skills/` directory is preserved
26
+ - 26 new tests (V-015 through V-031)
27
+
28
+ ### Changed
29
+
30
+ - Extracted shared `_skills_link_path` helper in `setup_cmd` so `setup` and `uninstall` resolve the Skills symlink location identically (no drift)
31
+
5
32
  ## [0.3.0] — 2026-05-28
6
33
 
34
+ ### Added
35
+
36
+ - `mpg setup` command: one-command MCP server registration + Agent Skills symlink creation. Replaces 3-4 manual steps with `pip install modern-python-guidance && mpg setup` (closes #60)
37
+ - CLI flags: `--mcp-only`, `--skills-only`, `--scope {user,local}`, `--project-dir`, `--dry-run`
38
+ - Project root auto-detection (`.claude/` → `.git/` → `pyproject.toml` upward search) for correct Skills symlink placement from subdirectories
39
+ - Idempotent operation: re-running `mpg setup` skips already-correct state, replaces stale/broken symlinks, errors on non-symlink blockers
40
+ - Partial success handling: MCP and Skills run independently; one failure does not block the other
41
+ - 33 new tests for setup command (V-001 through V-014 verification points)
42
+
43
+ ### Changed
44
+
45
+ - README Quick Start: reduced from 3 code blocks to 2 lines (`pip install` + `mpg setup`). Manual setup moved to collapsible `<details>` section
46
+
47
+ ## [0.2.3] — 2026-05-28
48
+
49
+ ### Fixed
50
+
51
+ - `fastapi-typed-state` guide: added missing Version Notes section (closes #13)
52
+ - `fastapi-typed-state` and `fastapi-lifespan` guides: corrected minimum version from FastAPI >= 0.93.0 to >= 0.94.0 (lifespan state dict requires Starlette >= 0.26.0, which FastAPI 0.93.0 excludes)
53
+
54
+ ## [0.2.2] — 2026-05-28
55
+
7
56
  ### Changed
8
57
 
9
58
  - Search response (MCP + CLI) now includes `tags`, `python`, `frequency`, and `snippet` fields for richer agent decision-making without requiring a follow-up retrieve call
@@ -81,7 +130,11 @@ Initial release.
81
130
  - Strict YAML-subset frontmatter parser (no PyYAML dependency)
82
131
  - GitHub Actions CI (pytest + ruff on Python 3.11, 3.12, 3.13)
83
132
 
133
+ [0.3.2]: https://github.com/yottayoshida/modern-python-guidance/releases/tag/v0.3.2
134
+ [0.3.1]: https://github.com/yottayoshida/modern-python-guidance/releases/tag/v0.3.1
84
135
  [0.3.0]: https://github.com/yottayoshida/modern-python-guidance/releases/tag/v0.3.0
136
+ [0.2.3]: https://github.com/yottayoshida/modern-python-guidance/releases/tag/v0.2.3
137
+ [0.2.2]: https://github.com/yottayoshida/modern-python-guidance/releases/tag/v0.2.2
85
138
  [0.2.1]: https://github.com/yottayoshida/modern-python-guidance/releases/tag/v0.2.1
86
139
  [0.2.0]: https://github.com/yottayoshida/modern-python-guidance/releases/tag/v0.2.0
87
140
  [0.1.2]: https://github.com/yottayoshida/modern-python-guidance/releases/tag/v0.1.2
@@ -15,7 +15,7 @@ src/modern_python_guidance/
15
15
 
16
16
  skills/modern-python-guidance/
17
17
  ├── SKILL.md # Agent Skills plugin entry point
18
- └── guides/ # 39 guide files by category
18
+ └── guides/ # 41 guide files by category
19
19
  ```
20
20
 
21
21
  See [docs/design.md](docs/design.md) for the full design document.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modern-python-guidance
3
- Version: 0.3.0
3
+ Version: 0.3.2
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
@@ -36,12 +36,12 @@ Description-Content-Type: text/markdown
36
36
  [![Python](https://img.shields.io/pypi/pyversions/modern-python-guidance.svg)](https://pypi.org/project/modern-python-guidance/)
37
37
  [![License](https://img.shields.io/github/license/yottayoshida/modern-python-guidance.svg)](LICENSE)
38
38
 
39
- Stop your AI from writing `typing.List`, `@validator`, and `setup.py`. 39 version-aware BAD/GOOD pattern guides that teach AI coding agents to write modern Python — delivered via MCP, CLI, or Agent Skills.
39
+ Stop your AI from writing `typing.List`, `@validator`, and `setup.py`. 41 version-aware BAD/GOOD pattern guides that teach AI coding agents to write modern Python — delivered via MCP, CLI, or Agent Skills.
40
40
 
41
41
  ## Highlights
42
42
 
43
43
  - **Measurable impact**: +14.7pp overall improvement in A/B benchmark via Agent Skills (38 scored items, [details](docs/benchmark-evaluation.md)). Largest variant (FastAPI, 32 items): Control 60.4% → Treatment 82.3%
44
- - **39 guides** across stdlib, Pydantic, FastAPI, Django, SQLAlchemy, pytest, and toolchain
44
+ - **41 guides** across stdlib, Pydantic, FastAPI, Django, SQLAlchemy, pytest, and toolchain
45
45
  - **Version-aware**: auto-detects your project's Python version and filters guides accordingly
46
46
  - **3 delivery methods**: MCP server, CLI, Agent Skills plugin
47
47
  - **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
@@ -50,15 +50,29 @@ Stop your AI from writing `typing.List`, `@validator`, and `setup.py`. 39 versio
50
50
 
51
51
  ## Quick start
52
52
 
53
- ### MCP (for AI coding agents)
53
+ ### Claude Code (recommended)
54
54
 
55
- Install, then register the MCP server with your agent:
55
+ ```bash
56
+ pip install modern-python-guidance
57
+ mpg setup
58
+ ```
59
+
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
+
62
+ ### CLI
56
63
 
57
64
  ```bash
58
65
  pip install modern-python-guidance
66
+ mpg search "pydantic validator"
67
+ mpg retrieve pydantic-v2-validators
59
68
  ```
60
69
 
61
- **Claude Code:**
70
+ `mpg` is the short alias for `modern-python-guidance`. Both work.
71
+
72
+ <details>
73
+ <summary>Manual setup / other agents</summary>
74
+
75
+ **MCP registration (Claude Code):**
62
76
  ```bash
63
77
  claude mcp add mpg -- mpg mcp
64
78
  ```
@@ -75,29 +89,36 @@ claude mcp add mpg -- mpg mcp
75
89
  }
76
90
  ```
77
91
 
78
- Your agent gets access to `search_guides`, `retrieve_guides`, `list_guides`, and `detect_python_version`.
79
-
80
- ### CLI
81
-
92
+ **Agent Skills symlink (Claude Code):**
82
93
  ```bash
83
- pip install modern-python-guidance
84
-
85
- # Search for a pattern
86
- mpg search "pydantic validator"
87
-
88
- # Get the full guide
89
- mpg retrieve pydantic-v2-validators
94
+ mpg setup --skills-only
90
95
  ```
91
96
 
92
- ### Agent Skills (Claude Code plugin)
97
+ **`mpg setup` flags:**
98
+ | Flag | Purpose |
99
+ |------|---------|
100
+ | `--mcp-only` | MCP registration only |
101
+ | `--skills-only` | Agent Skills symlink only |
102
+ | `--scope {user,local}` | MCP scope (default: user) |
103
+ | `--project-dir PATH` | Target project for Skills symlink |
104
+ | `--dry-run` | Show what would be done |
93
105
 
106
+ **Uninstall** — reverse `mpg setup` (deregister the MCP server and unlink Agent Skills):
94
107
  ```bash
95
- # Symlink into your project
96
- SKILL_DIR=$(python -c "from pathlib import Path; import modern_python_guidance; print(Path(modern_python_guidance.__file__).parent / 'skills' / 'modern-python-guidance')")
97
- ln -s "$SKILL_DIR" your-project/.claude/skills/modern-python-guidance
108
+ mpg uninstall # remove both
109
+ mpg uninstall --dry-run # preview what would be removed
98
110
  ```
99
111
 
100
- `mpg` is the short alias for `modern-python-guidance`. Both work.
112
+ | Flag | Purpose |
113
+ |------|---------|
114
+ | `--mcp-only` | MCP deregistration only |
115
+ | `--skills-only` | Agent Skills unlink only |
116
+ | `--project-dir PATH` | Target project for the Skills symlink |
117
+ | `--dry-run` | Show what would be done |
118
+
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
+
121
+ </details>
101
122
 
102
123
  ## CLI usage
103
124
 
@@ -123,15 +144,15 @@ mpg search "typing" --format json | jq '.[0].id'
123
144
 
124
145
  ## Guide coverage
125
146
 
126
- 39 guides across 3 layers:
147
+ 41 guides across 3 layers:
127
148
 
128
149
  | Layer | Categories | Count | Examples |
129
150
  |-------|-----------|-------|---------|
130
- | **1 — stdlib** | typing, async, stdlib, data-structures | 16 | `list` over `List`, `match`/`case`, `TaskGroup` |
151
+ | **1 — stdlib** | typing, async, stdlib, data-structures | 18 | `list` over `List`, `match`/`case`, `TaskGroup`, deferred annotations, t-strings |
131
152
  | **2 — frameworks** | pydantic, fastapi, httpx, django, sqlalchemy, pytest | 18 | Pydantic V2 migration, SQLAlchemy 2.0 style, `Annotated[Depends]` |
132
153
  | **3 — toolchain** | toolchain | 5 | `uv` over `pip`, `ruff` over flake8, `pickle` avoidance |
133
154
 
134
- Run `mpg list` to see all 39 guides, or [browse them on GitHub](skills/modern-python-guidance/guides/).
155
+ Run `mpg list` to see all 41 guides, or [browse them on GitHub](skills/modern-python-guidance/guides/).
135
156
 
136
157
  ## Version-aware filtering
137
158
 
@@ -5,12 +5,12 @@
5
5
  [![Python](https://img.shields.io/pypi/pyversions/modern-python-guidance.svg)](https://pypi.org/project/modern-python-guidance/)
6
6
  [![License](https://img.shields.io/github/license/yottayoshida/modern-python-guidance.svg)](LICENSE)
7
7
 
8
- Stop your AI from writing `typing.List`, `@validator`, and `setup.py`. 39 version-aware BAD/GOOD pattern guides that teach AI coding agents to write modern Python — delivered via MCP, CLI, or Agent Skills.
8
+ Stop your AI from writing `typing.List`, `@validator`, and `setup.py`. 41 version-aware BAD/GOOD pattern guides that teach AI coding agents to write modern Python — delivered via MCP, CLI, or Agent Skills.
9
9
 
10
10
  ## Highlights
11
11
 
12
12
  - **Measurable impact**: +14.7pp overall improvement in A/B benchmark via Agent Skills (38 scored items, [details](docs/benchmark-evaluation.md)). Largest variant (FastAPI, 32 items): Control 60.4% → Treatment 82.3%
13
- - **39 guides** across stdlib, Pydantic, FastAPI, Django, SQLAlchemy, pytest, and toolchain
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
15
  - **3 delivery methods**: MCP server, CLI, Agent Skills plugin
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
@@ -19,15 +19,29 @@ Stop your AI from writing `typing.List`, `@validator`, and `setup.py`. 39 versio
19
19
 
20
20
  ## Quick start
21
21
 
22
- ### MCP (for AI coding agents)
22
+ ### Claude Code (recommended)
23
23
 
24
- Install, then register the MCP server with your agent:
24
+ ```bash
25
+ pip install modern-python-guidance
26
+ mpg setup
27
+ ```
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.
30
+
31
+ ### CLI
25
32
 
26
33
  ```bash
27
34
  pip install modern-python-guidance
35
+ mpg search "pydantic validator"
36
+ mpg retrieve pydantic-v2-validators
28
37
  ```
29
38
 
30
- **Claude Code:**
39
+ `mpg` is the short alias for `modern-python-guidance`. Both work.
40
+
41
+ <details>
42
+ <summary>Manual setup / other agents</summary>
43
+
44
+ **MCP registration (Claude Code):**
31
45
  ```bash
32
46
  claude mcp add mpg -- mpg mcp
33
47
  ```
@@ -44,29 +58,36 @@ claude mcp add mpg -- mpg mcp
44
58
  }
45
59
  ```
46
60
 
47
- Your agent gets access to `search_guides`, `retrieve_guides`, `list_guides`, and `detect_python_version`.
48
-
49
- ### CLI
50
-
61
+ **Agent Skills symlink (Claude Code):**
51
62
  ```bash
52
- pip install modern-python-guidance
53
-
54
- # Search for a pattern
55
- mpg search "pydantic validator"
56
-
57
- # Get the full guide
58
- mpg retrieve pydantic-v2-validators
63
+ mpg setup --skills-only
59
64
  ```
60
65
 
61
- ### Agent Skills (Claude Code plugin)
66
+ **`mpg setup` flags:**
67
+ | Flag | Purpose |
68
+ |------|---------|
69
+ | `--mcp-only` | MCP registration only |
70
+ | `--skills-only` | Agent Skills symlink only |
71
+ | `--scope {user,local}` | MCP scope (default: user) |
72
+ | `--project-dir PATH` | Target project for Skills symlink |
73
+ | `--dry-run` | Show what would be done |
62
74
 
75
+ **Uninstall** — reverse `mpg setup` (deregister the MCP server and unlink Agent Skills):
63
76
  ```bash
64
- # Symlink into your project
65
- SKILL_DIR=$(python -c "from pathlib import Path; import modern_python_guidance; print(Path(modern_python_guidance.__file__).parent / 'skills' / 'modern-python-guidance')")
66
- ln -s "$SKILL_DIR" your-project/.claude/skills/modern-python-guidance
77
+ mpg uninstall # remove both
78
+ mpg uninstall --dry-run # preview what would be removed
67
79
  ```
68
80
 
69
- `mpg` is the short alias for `modern-python-guidance`. Both work.
81
+ | Flag | Purpose |
82
+ |------|---------|
83
+ | `--mcp-only` | MCP deregistration only |
84
+ | `--skills-only` | Agent Skills unlink only |
85
+ | `--project-dir PATH` | Target project for the Skills symlink |
86
+ | `--dry-run` | Show what would be done |
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.
89
+
90
+ </details>
70
91
 
71
92
  ## CLI usage
72
93
 
@@ -92,15 +113,15 @@ mpg search "typing" --format json | jq '.[0].id'
92
113
 
93
114
  ## Guide coverage
94
115
 
95
- 39 guides across 3 layers:
116
+ 41 guides across 3 layers:
96
117
 
97
118
  | Layer | Categories | Count | Examples |
98
119
  |-------|-----------|-------|---------|
99
- | **1 — stdlib** | typing, async, stdlib, data-structures | 16 | `list` over `List`, `match`/`case`, `TaskGroup` |
120
+ | **1 — stdlib** | typing, async, stdlib, data-structures | 18 | `list` over `List`, `match`/`case`, `TaskGroup`, deferred annotations, t-strings |
100
121
  | **2 — frameworks** | pydantic, fastapi, httpx, django, sqlalchemy, pytest | 18 | Pydantic V2 migration, SQLAlchemy 2.0 style, `Annotated[Depends]` |
101
122
  | **3 — toolchain** | toolchain | 5 | `uv` over `pip`, `ruff` over flake8, `pickle` avoidance |
102
123
 
103
- Run `mpg list` to see all 39 guides, or [browse them on GitHub](skills/modern-python-guidance/guides/).
124
+ Run `mpg list` to see all 41 guides, or [browse them on GitHub](skills/modern-python-guidance/guides/).
104
125
 
105
126
  ## Version-aware filtering
106
127
 
@@ -52,7 +52,7 @@ LLMs frequently generate outdated Python patterns: `typing.List` instead of `lis
52
52
  │ typing/ async/ stdlib/ data-structures/ │
53
53
  │ pydantic/ fastapi/ httpx/ toolchain/ │
54
54
  │ django/ sqlalchemy/ pytest/ │
55
- │ (39 guide files) │
55
+ │ (41 guide files) │
56
56
  └─────────────────────────────────────────────────────────┘
57
57
  ```
58
58
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "modern-python-guidance"
7
- version = "0.3.0"
7
+ version = "0.3.2"
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"
@@ -55,11 +55,11 @@ Pre-generation design patterns that Ruff cannot auto-fix. Apply these transforma
55
55
  - `subprocess.run(f"cmd {arg}", shell=True)` → `subprocess.run(["cmd", arg], check=True)`
56
56
  - Caveat: `shell=True` is valid when pipes/globs are needed; use `shlex.quote()` for user input
57
57
 
58
- ## All 39 guides by category
58
+ ## All 41 guides by category
59
59
 
60
- - **typing** (6): `use-builtin-generics`, `union-syntax`, `type-parameter-syntax`, `override-decorator`, `typeis-vs-typeguard`, `paramspec-decorators`
60
+ - **typing** (7): `use-builtin-generics`, `union-syntax`, `type-parameter-syntax`, `override-decorator`, `typeis-vs-typeguard`, `paramspec-decorators`, `deferred-annotations`
61
61
  - **async** (3): `taskgroup-over-gather`, `exception-groups`, `async-timeout-context`
62
- - **stdlib** (4): `datetime-utc`, `pathlib-over-os-path`, `tomllib-builtin`, `removeprefix-removesuffix`
62
+ - **stdlib** (5): `datetime-utc`, `pathlib-over-os-path`, `tomllib-builtin`, `removeprefix-removesuffix`, `template-strings`
63
63
  - **data-structures** (3): `dict-merge-operator`, `match-case-patterns`, `dataclass-modern`
64
64
  - **pydantic** (4): `pydantic-v2-validators`, `pydantic-v2-config`, `pydantic-v2-model-api`, `pydantic-v2-serialization`
65
65
  - **fastapi** (3): `fastapi-lifespan`, `fastapi-annotated-depends`, `fastapi-typed-state`
@@ -68,7 +68,7 @@ async def root(request: Request):
68
68
 
69
69
  ## Version Notes
70
70
 
71
- - Works on Python 3.9+ with FastAPI >= 0.93.0
71
+ - Works on Python 3.9+ with FastAPI >= 0.94.0 (lifespan state dict requires Starlette >= 0.26.0)
72
72
  - `AsyncIterator` moved from `typing` to `collections.abc` in 3.9
73
73
 
74
74
  ## References
@@ -70,6 +70,11 @@ async def root(request: Request):
70
70
  - `dataclass` or `TypedDict` documents the expected shape
71
71
  - Resource cleanup is guaranteed by the context manager
72
72
 
73
+ ## Version Notes
74
+
75
+ - Lifespan state dict requires FastAPI >= 0.94.0 (Starlette >= 0.26.0)
76
+ - `@dataclass(slots=True)` requires Python 3.10+; use plain `@dataclass` on 3.9
77
+
73
78
  ## References
74
79
 
75
80
  - [FastAPI Lifespan State](https://fastapi.tiangolo.com/advanced/events/#lifespan-state)
@@ -0,0 +1,70 @@
1
+ ---
2
+ id: template-strings
3
+ title: Use Template Strings (t-strings) for Structured String Processing
4
+ category: stdlib
5
+ layer: 1
6
+ tags:
7
+ - strings
8
+ - templates
9
+ - security
10
+ - sql
11
+ aliases:
12
+ - t-string
13
+ - 't"'
14
+ - Template
15
+ - string.templatelib
16
+ python: ">=3.14"
17
+ frequency: medium
18
+ pep: 750
19
+ ---
20
+
21
+ # Use Template Strings for Structured String Processing
22
+
23
+ Python 3.14 introduces t-strings (PEP 750): a `t"..."` prefix that produces a `Template` object instead of a `str`. Pass the `Template` to a processing function that separates literal text from interpolated values, enabling safe parameterization for SQL, HTML, shell commands, and other injection-sensitive contexts.
24
+
25
+ ## BAD
26
+
27
+ ```python
28
+ def query_user(db, user_id: str) -> dict:
29
+ sql = f"SELECT * FROM users WHERE id = '{user_id}'"
30
+ return db.execute(sql).fetchone()
31
+ ```
32
+
33
+ ## GOOD
34
+
35
+ ```python
36
+ from string.templatelib import Interpolation
37
+
38
+ def sql(template) -> tuple[str, list]:
39
+ parts, params = [], []
40
+ for item in template:
41
+ if isinstance(item, Interpolation):
42
+ parts.append("?")
43
+ params.append(item.value)
44
+ else:
45
+ parts.append(item)
46
+ return "".join(parts), params
47
+
48
+ def query_user(db, user_id: str) -> dict:
49
+ query, params = sql(t"SELECT * FROM users WHERE id = {user_id}")
50
+ return db.execute(query, params).fetchone()
51
+ ```
52
+
53
+ ## Why
54
+
55
+ - f-strings produce a final `str` with interpolated values already baked in, making it impossible to distinguish user input from literal SQL after the fact
56
+ - t-strings produce a `Template` that preserves the structure: literal segments as strings, interpolated values as `Interpolation` objects with `value`, `expression`, `conversion`, and `format_spec` attributes
57
+ - A processing function can then route each interpolated value through proper escaping or parameterization, preventing injection by construction
58
+ - t-strings do NOT make strings safe by themselves -- safety comes entirely from the processing function that receives the `Template`
59
+
60
+ ## Version Notes
61
+
62
+ - 3.14+: `t"..."` syntax and `string.templatelib` module (`Template`, `Interpolation`)
63
+ - Pre-3.14: Use parameterized queries directly (`db.execute("SELECT ... WHERE id = ?", [user_id])`) or template engines with auto-escaping
64
+ - For simple string formatting where injection is not a concern, f-strings remain the appropriate choice
65
+ - `Template` objects do not render to `str` automatically -- a processing function is always required
66
+
67
+ ## References
68
+
69
+ - [PEP 750 -- Template Strings](https://peps.python.org/pep-0750/)
70
+ - [string.templatelib documentation](https://docs.python.org/3/library/string.templatelib.html)
@@ -0,0 +1,63 @@
1
+ ---
2
+ id: deferred-annotations
3
+ title: 'Drop "from __future__ import annotations" on Python 3.14+'
4
+ category: typing
5
+ layer: 1
6
+ tags:
7
+ - type-hints
8
+ - annotations
9
+ - forward-reference
10
+ aliases:
11
+ - "from __future__ import annotations"
12
+ - "__future__.annotations"
13
+ - ForwardRef
14
+ python: ">=3.14"
15
+ frequency: high
16
+ pep: 649
17
+ ---
18
+
19
+ # Drop `from __future__ import annotations` on Python 3.14+
20
+
21
+ On Python 3.14+, annotations are lazily evaluated by default (PEP 649). The `from __future__ import annotations` import is no longer needed for forward references in 3.14-only projects.
22
+
23
+ ## BAD
24
+
25
+ ```python
26
+ from __future__ import annotations
27
+
28
+ class Tree:
29
+ left: Tree | None = None
30
+ right: Tree | None = None
31
+
32
+ def children(self) -> list[Tree]:
33
+ return [c for c in (self.left, self.right) if c]
34
+ ```
35
+
36
+ ## GOOD
37
+
38
+ ```python
39
+ class Tree:
40
+ left: Tree | None = None
41
+ right: Tree | None = None
42
+
43
+ def children(self) -> list[Tree]:
44
+ return [c for c in (self.left, self.right) if c]
45
+ ```
46
+
47
+ ## Why
48
+
49
+ - Python 3.14 evaluates annotations lazily by default, so forward references like `Tree` inside the `Tree` class body just work
50
+ - The future import forces all-string semantics (PEP 563), which is different from 3.14's lazy evaluation and can interact poorly with runtime annotation consumers
51
+ - Removing the unnecessary import keeps the module cleaner and avoids the subtle behavioral difference between stringified and lazily-evaluated annotations
52
+
53
+ ## Version Notes
54
+
55
+ - 3.14+: Annotations are lazily evaluated by default. Remove `from __future__ import annotations` in 3.14-only projects
56
+ - Pre-3.14: The future import remains the correct way to enable string annotations for forward references
57
+ - `from __future__ import annotations` is deprecated but NOT removed in 3.14. It still works but forces all-string semantics. Removal is not expected before Python 3.13 EOL (~2029)
58
+ - Libraries that read annotations at runtime (`typing.get_type_hints()`, Pydantic, FastAPI, attrs) may need updates for 3.14's lazy evaluation. Verify library compatibility before removing the import in projects that depend on runtime annotation inspection
59
+
60
+ ## References
61
+
62
+ - [PEP 649 -- Deferred Evaluation of Annotations Using Descriptors](https://peps.python.org/pep-0649/)
63
+ - [PEP 749 -- Implementing PEP 649](https://peps.python.org/pep-0749/)
@@ -1,3 +1,3 @@
1
1
  """Modern Python Guidance — version-aware BAD/GOOD pattern guides for AI coding agents."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.3.2"
@@ -65,6 +65,32 @@ def main(argv: list[str] | None = None) -> None:
65
65
  # mcp
66
66
  subparsers.add_parser("mcp", help="Start MCP server (JSON-RPC over stdio)")
67
67
 
68
+ # setup
69
+ p_setup = subparsers.add_parser(
70
+ "setup", help="Register MCP server and link Agent Skills",
71
+ )
72
+ 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
+ p_setup.add_argument(
75
+ "--scope", choices=["user", "local"], default="user",
76
+ help="MCP scope (default: user)",
77
+ )
78
+ p_setup.add_argument(
79
+ "--project-dir", type=Path, help="Project directory for Skills symlink",
80
+ )
81
+ p_setup.add_argument("--dry-run", action="store_true", help="Show what would be done")
82
+
83
+ # uninstall
84
+ p_uninstall = subparsers.add_parser(
85
+ "uninstall", help="Reverse 'setup': deregister MCP server and unlink Agent Skills",
86
+ )
87
+ 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
+ p_uninstall.add_argument(
90
+ "--project-dir", type=Path, help="Project directory for Skills symlink",
91
+ )
92
+ p_uninstall.add_argument("--dry-run", action="store_true", help="Show what would be done")
93
+
68
94
  args = parser.parse_args(argv)
69
95
 
70
96
  if args.command is None:
@@ -86,6 +112,10 @@ def main(argv: list[str] | None = None) -> None:
86
112
  _cmd_detect_version(args)
87
113
  elif args.command == "mcp":
88
114
  _cmd_mcp()
115
+ elif args.command == "setup":
116
+ _cmd_setup(args)
117
+ elif args.command == "uninstall":
118
+ _cmd_uninstall(args)
89
119
  except BrokenPipeError:
90
120
  sys.exit(0)
91
121
 
@@ -215,3 +245,28 @@ def _cmd_mcp() -> None:
215
245
  from modern_python_guidance.mcp_server import serve
216
246
 
217
247
  serve()
248
+
249
+
250
+ def _cmd_setup(args: argparse.Namespace) -> None:
251
+ from modern_python_guidance.setup_cmd import run_setup
252
+
253
+ code = run_setup(
254
+ scope=args.scope,
255
+ mcp_only=args.mcp_only,
256
+ skills_only=args.skills_only,
257
+ project_dir=args.project_dir,
258
+ dry_run=args.dry_run,
259
+ )
260
+ sys.exit(code)
261
+
262
+
263
+ def _cmd_uninstall(args: argparse.Namespace) -> None:
264
+ from modern_python_guidance.uninstall_cmd import run_uninstall
265
+
266
+ code = run_uninstall(
267
+ mcp_only=args.mcp_only,
268
+ skills_only=args.skills_only,
269
+ project_dir=args.project_dir,
270
+ dry_run=args.dry_run,
271
+ )
272
+ sys.exit(code)
@@ -121,8 +121,8 @@ TOOLS = [
121
121
  "guide_ids": {
122
122
  "type": "array",
123
123
  "items": {"type": "string"},
124
- "description": "Guide IDs to retrieve (max 39)",
125
- "maxItems": 39,
124
+ "description": "Guide IDs to retrieve (max 41)",
125
+ "maxItems": 41,
126
126
  },
127
127
  "python_version": {
128
128
  "type": "string",
@@ -269,8 +269,8 @@ def _tool_retrieve(arguments: dict) -> dict:
269
269
  guide_ids = arguments.get("guide_ids", [])
270
270
  if not guide_ids:
271
271
  return _tool_result("guide_ids is required and must not be empty", is_error=True)
272
- if len(guide_ids) > 39:
273
- return _tool_result("guide_ids exceeds maximum of 39", is_error=True)
272
+ if len(guide_ids) > 41:
273
+ return _tool_result("guide_ids exceeds maximum of 41", is_error=True)
274
274
 
275
275
  pv = arguments.get("python_version")
276
276
  err = _validate_python_version(pv)