modern-python-guidance 0.3.0__tar.gz → 0.3.1__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 (112) hide show
  1. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/CHANGELOG.md +38 -0
  2. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/PKG-INFO +41 -20
  3. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/README.md +40 -19
  4. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/pyproject.toml +1 -1
  5. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/fastapi/fastapi-lifespan.md +1 -1
  6. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/fastapi/fastapi-typed-state.md +5 -0
  7. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/src/modern_python_guidance/__init__.py +1 -1
  8. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/src/modern_python_guidance/cli.py +55 -0
  9. modern_python_guidance-0.3.1/src/modern_python_guidance/setup_cmd.py +186 -0
  10. modern_python_guidance-0.3.1/src/modern_python_guidance/uninstall_cmd.py +171 -0
  11. modern_python_guidance-0.3.1/tests/test_setup.py +409 -0
  12. modern_python_guidance-0.3.1/tests/test_uninstall.py +371 -0
  13. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/.github/workflows/ci.yml +0 -0
  14. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/.github/workflows/publish.yml +0 -0
  15. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/.gitignore +0 -0
  16. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/CONTRIBUTING.md +0 -0
  17. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/LICENSE +0 -0
  18. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/LICENSE-MIT +0 -0
  19. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/SECURITY.md +0 -0
  20. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-a-modern/pyproject.toml +0 -0
  21. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-a-modern/src/app.py +0 -0
  22. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-a-modern/src/config.py +0 -0
  23. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-a-modern/src/crawler.py +0 -0
  24. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-a-modern/src/models.py +0 -0
  25. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-a-modern/src/scanner.py +0 -0
  26. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-a-modern/src/utils.py +0 -0
  27. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-a-outdated/pyproject.toml +0 -0
  28. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-a-outdated/setup.py +0 -0
  29. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-a-outdated/src/app.py +0 -0
  30. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-a-outdated/src/config.py +0 -0
  31. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-a-outdated/src/crawler.py +0 -0
  32. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-a-outdated/src/models.py +0 -0
  33. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-a-outdated/src/scanner.py +0 -0
  34. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-a-outdated/src/utils.py +0 -0
  35. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-b-modern/myapp/models.py +0 -0
  36. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-b-modern/myapp/views.py +0 -0
  37. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-b-outdated/myapp/models.py +0 -0
  38. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-b-outdated/myapp/views.py +0 -0
  39. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-c-modern/tests/test_calculator.py +0 -0
  40. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/fixtures/variant-c-outdated/tests/test_calculator.py +0 -0
  41. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/mcp-config.json +0 -0
  42. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/prompt-v2.txt +0 -0
  43. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/prompt-v3-mcp.txt +0 -0
  44. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/prompt-v3.txt +0 -0
  45. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/prompt-v4-a.txt +0 -0
  46. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/prompt-v4-b.txt +0 -0
  47. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/prompt-v4-c.txt +0 -0
  48. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/prompt.txt +0 -0
  49. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/run-mcp.sh +0 -0
  50. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/run-v4.sh +0 -0
  51. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/run.sh +0 -0
  52. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/score-v2.sh +0 -0
  53. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/score-v3.sh +0 -0
  54. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/score-v4.sh +0 -0
  55. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/score.sh +0 -0
  56. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/bench/test-scorer.sh +0 -0
  57. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/docs/benchmark-evaluation.md +0 -0
  58. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/docs/benchmark-procedure.md +0 -0
  59. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/docs/design.md +0 -0
  60. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/SKILL.md +0 -0
  61. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/async/async-timeout-context.md +0 -0
  62. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/async/exception-groups.md +0 -0
  63. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/async/taskgroup-over-gather.md +0 -0
  64. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/data-structures/dataclass-modern.md +0 -0
  65. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/data-structures/dict-merge-operator.md +0 -0
  66. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/data-structures/match-case-patterns.md +0 -0
  67. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/django/django-async-views.md +0 -0
  68. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/django/django-check-constraints.md +0 -0
  69. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/django/django-json-field.md +0 -0
  70. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/fastapi/fastapi-annotated-depends.md +0 -0
  71. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/httpx/httpx-async-client-reuse.md +0 -0
  72. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/httpx/httpx-streaming.md +0 -0
  73. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-config.md +0 -0
  74. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-model-api.md +0 -0
  75. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-serialization.md +0 -0
  76. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-validators.md +0 -0
  77. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/pytest/pytest-parametrize.md +0 -0
  78. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/pytest/pytest-raises-match.md +0 -0
  79. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/pytest/pytest-tmp-path.md +0 -0
  80. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-2-style.md +0 -0
  81. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-async-session.md +0 -0
  82. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-mapped-column.md +0 -0
  83. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/stdlib/datetime-utc.md +0 -0
  84. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/stdlib/pathlib-over-os-path.md +0 -0
  85. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/stdlib/removeprefix-removesuffix.md +0 -0
  86. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/stdlib/tomllib-builtin.md +0 -0
  87. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/toolchain/no-pickle.md +0 -0
  88. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/toolchain/pyproject-toml-over-setup.md +0 -0
  89. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/toolchain/ruff-over-flake8.md +0 -0
  90. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/toolchain/safe-subprocess.md +0 -0
  91. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/toolchain/uv-over-pip.md +0 -0
  92. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/typing/override-decorator.md +0 -0
  93. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/typing/paramspec-decorators.md +0 -0
  94. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/typing/type-parameter-syntax.md +0 -0
  95. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/typing/typeis-vs-typeguard.md +0 -0
  96. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/typing/union-syntax.md +0 -0
  97. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/skills/modern-python-guidance/guides/typing/use-builtin-generics.md +0 -0
  98. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/src/modern_python_guidance/__main__.py +0 -0
  99. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/src/modern_python_guidance/compat.py +0 -0
  100. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/src/modern_python_guidance/frontmatter.py +0 -0
  101. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/src/modern_python_guidance/guide_index.py +0 -0
  102. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/src/modern_python_guidance/mcp_server.py +0 -0
  103. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/src/modern_python_guidance/retrieve.py +0 -0
  104. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/src/modern_python_guidance/search.py +0 -0
  105. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/src/modern_python_guidance/version_detect.py +0 -0
  106. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/tests/test_cli_integration.py +0 -0
  107. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/tests/test_frontmatter.py +0 -0
  108. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/tests/test_mcp_server.py +0 -0
  109. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/tests/test_retrieve.py +0 -0
  110. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/tests/test_search.py +0 -0
  111. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/tests/test_skill_sync.py +0 -0
  112. {modern_python_guidance-0.3.0 → modern_python_guidance-0.3.1}/tests/test_version_detect.py +0 -0
@@ -2,8 +2,44 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.3.1] — 2026-05-29
6
+
7
+ ### Added
8
+
9
+ - `mpg uninstall` command: reverses `mpg setup` by deregistering the MCP server and removing the Agent Skills symlink in one command (closes #63)
10
+ - CLI flags: `--mcp-only`, `--skills-only`, `--project-dir`, `--dry-run` (no `--scope`; uninstall clears every scope `setup` can write to)
11
+ - 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
12
+ - 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
13
+ - 26 new tests (V-015 through V-031)
14
+
15
+ ### Changed
16
+
17
+ - Extracted shared `_skills_link_path` helper in `setup_cmd` so `setup` and `uninstall` resolve the Skills symlink location identically (no drift)
18
+
5
19
  ## [0.3.0] — 2026-05-28
6
20
 
21
+ ### Added
22
+
23
+ - `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)
24
+ - CLI flags: `--mcp-only`, `--skills-only`, `--scope {user,local}`, `--project-dir`, `--dry-run`
25
+ - Project root auto-detection (`.claude/` → `.git/` → `pyproject.toml` upward search) for correct Skills symlink placement from subdirectories
26
+ - Idempotent operation: re-running `mpg setup` skips already-correct state, replaces stale/broken symlinks, errors on non-symlink blockers
27
+ - Partial success handling: MCP and Skills run independently; one failure does not block the other
28
+ - 33 new tests for setup command (V-001 through V-014 verification points)
29
+
30
+ ### Changed
31
+
32
+ - README Quick Start: reduced from 3 code blocks to 2 lines (`pip install` + `mpg setup`). Manual setup moved to collapsible `<details>` section
33
+
34
+ ## [0.2.3] — 2026-05-28
35
+
36
+ ### Fixed
37
+
38
+ - `fastapi-typed-state` guide: added missing Version Notes section (closes #13)
39
+ - `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)
40
+
41
+ ## [0.2.2] — 2026-05-28
42
+
7
43
  ### Changed
8
44
 
9
45
  - Search response (MCP + CLI) now includes `tags`, `python`, `frequency`, and `snippet` fields for richer agent decision-making without requiring a follow-up retrieve call
@@ -82,6 +118,8 @@ Initial release.
82
118
  - GitHub Actions CI (pytest + ruff on Python 3.11, 3.12, 3.13)
83
119
 
84
120
  [0.3.0]: https://github.com/yottayoshida/modern-python-guidance/releases/tag/v0.3.0
121
+ [0.2.3]: https://github.com/yottayoshida/modern-python-guidance/releases/tag/v0.2.3
122
+ [0.2.2]: https://github.com/yottayoshida/modern-python-guidance/releases/tag/v0.2.2
85
123
  [0.2.1]: https://github.com/yottayoshida/modern-python-guidance/releases/tag/v0.2.1
86
124
  [0.2.0]: https://github.com/yottayoshida/modern-python-guidance/releases/tag/v0.2.0
87
125
  [0.1.2]: https://github.com/yottayoshida/modern-python-guidance/releases/tag/v0.1.2
@@ -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.1
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
@@ -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
 
@@ -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
 
@@ -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.1"
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"
@@ -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)
@@ -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.1"
@@ -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)
@@ -0,0 +1,186 @@
1
+ """Automate MCP server registration and Agent Skills symlink creation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.resources
6
+ import os
7
+ import shlex
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ SKILLS_LINK_NAME = "modern-python-guidance"
14
+ MCP_SERVER_NAME = "mpg"
15
+
16
+
17
+ def _find_skills_dir() -> Path:
18
+ """Resolve the bundled skills directory (package install or editable)."""
19
+ try:
20
+ pkg = importlib.resources.files("modern_python_guidance") / "skills"
21
+ skills_path = Path(str(pkg)) / SKILLS_LINK_NAME
22
+ if skills_path.is_dir():
23
+ return skills_path
24
+ except (TypeError, FileNotFoundError):
25
+ pass
26
+
27
+ src_root = Path(__file__).resolve().parent.parent.parent
28
+ dev_path = src_root / "skills" / SKILLS_LINK_NAME
29
+ if dev_path.is_dir():
30
+ return dev_path
31
+
32
+ msg = "Cannot locate bundled skills directory"
33
+ raise FileNotFoundError(msg)
34
+
35
+
36
+ def _find_project_root(start: Path | None = None) -> Path:
37
+ """Walk upward from *start* to find the project root."""
38
+ current = (start or Path.cwd()).resolve()
39
+ markers = [".claude", ".git", "pyproject.toml"]
40
+
41
+ for marker in markers:
42
+ d = current
43
+ while True:
44
+ candidate = d / marker
45
+ if candidate.exists():
46
+ return d
47
+ parent = d.parent
48
+ if parent == d:
49
+ break
50
+ d = parent
51
+
52
+ return current
53
+
54
+
55
+ def _skills_link_path(project_dir: Path | None = None) -> Path:
56
+ """Resolve the Agent Skills symlink path: ``<root>/.claude/skills/<name>``.
57
+
58
+ Single source of truth for where the skills symlink lives, shared by
59
+ ``setup_skills`` (creation) and ``uninstall_skills`` (removal) so the two
60
+ operations cannot drift in how they locate the link.
61
+ """
62
+ root = project_dir or _find_project_root()
63
+ return root / ".claude" / "skills" / SKILLS_LINK_NAME
64
+
65
+
66
+ def setup_mcp(
67
+ *,
68
+ scope: str = "user",
69
+ dry_run: bool = False,
70
+ ) -> bool:
71
+ """Register the MCP server with Claude Code. Returns True on success."""
72
+ args = ["mcp", "add", "--scope", scope, MCP_SERVER_NAME, "--", "mpg", "mcp"]
73
+
74
+ if dry_run:
75
+ print(f"Would run: claude {' '.join(args)}")
76
+ return True
77
+
78
+ claude = shutil.which("claude")
79
+ if claude is None:
80
+ print("Error: 'claude' command not found.", file=sys.stderr)
81
+ print("Install Claude Code: https://claude.ai/download", file=sys.stderr)
82
+ print(
83
+ "Run 'mpg setup --skills-only' to set up Agent Skills without MCP.",
84
+ file=sys.stderr,
85
+ )
86
+ return False
87
+
88
+ cmd = [claude, *args]
89
+
90
+ try:
91
+ result = subprocess.run(cmd, capture_output=True, timeout=30)
92
+ except subprocess.TimeoutExpired:
93
+ print("Error: 'claude mcp add' timed out after 30 seconds.", file=sys.stderr)
94
+ return False
95
+
96
+ if result.returncode != 0:
97
+ stderr_text = result.stderr.decode(errors="replace").strip()
98
+ print(f"Error: 'claude mcp add' failed (exit {result.returncode}).", file=sys.stderr)
99
+ if stderr_text:
100
+ print(stderr_text, file=sys.stderr)
101
+ return False
102
+
103
+ print(f"MCP server registered with Claude Code ({scope} scope).")
104
+ return True
105
+
106
+
107
+ def setup_skills(
108
+ *,
109
+ project_dir: Path | None = None,
110
+ dry_run: bool = False,
111
+ ) -> bool:
112
+ """Create Agent Skills symlink. Returns True on success."""
113
+ try:
114
+ source = _find_skills_dir()
115
+ except FileNotFoundError as e:
116
+ print(f"Error: {e}", file=sys.stderr)
117
+ return False
118
+
119
+ root = project_dir or _find_project_root()
120
+ link_path = _skills_link_path(project_dir)
121
+ skills_parent = link_path.parent
122
+
123
+ if dry_run:
124
+ print(f"Would link: {link_path} -> {source}")
125
+ return True
126
+
127
+ if link_path.is_symlink():
128
+ current_target = Path(os.readlink(link_path))
129
+ if current_target == source or link_path.resolve() == source.resolve():
130
+ print(f"Agent Skills already linked at {link_path.relative_to(root)}")
131
+ return True
132
+ # Stale or broken symlink — replace
133
+ link_path.unlink()
134
+ elif link_path.exists():
135
+ print(
136
+ f"Error: {link_path.relative_to(root)} exists and is not a symlink.",
137
+ file=sys.stderr,
138
+ )
139
+ print(
140
+ f"Remove it manually: rm -rf {shlex.quote(str(link_path))}",
141
+ file=sys.stderr,
142
+ )
143
+ return False
144
+
145
+ try:
146
+ skills_parent.mkdir(parents=True, exist_ok=True)
147
+ os.symlink(source, link_path)
148
+ except OSError as e:
149
+ print(f"Error creating symlink: {e}", file=sys.stderr)
150
+ return False
151
+
152
+ print(f"Agent Skills linked to {link_path.relative_to(root)}")
153
+ return True
154
+
155
+
156
+ def run_setup(
157
+ *,
158
+ scope: str = "user",
159
+ mcp_only: bool = False,
160
+ skills_only: bool = False,
161
+ project_dir: Path | None = None,
162
+ dry_run: bool = False,
163
+ ) -> int:
164
+ """Run the full setup sequence. Returns exit code (0=success, 1=failure)."""
165
+ if mcp_only and skills_only:
166
+ print("Error: --mcp-only and --skills-only are mutually exclusive.", file=sys.stderr)
167
+ return 1
168
+
169
+ do_mcp = not skills_only
170
+ do_skills = not mcp_only
171
+
172
+ mcp_ok = True
173
+ skills_ok = True
174
+
175
+ if do_mcp:
176
+ mcp_ok = setup_mcp(scope=scope, dry_run=dry_run)
177
+
178
+ if do_skills:
179
+ skills_ok = setup_skills(project_dir=project_dir, dry_run=dry_run)
180
+
181
+ if mcp_ok and skills_ok:
182
+ if not dry_run and do_mcp and do_skills:
183
+ print("Ready. Start Claude Code to use mpg guides.")
184
+ return 0
185
+
186
+ return 1