modern-python-guidance 0.4.1__tar.gz → 0.4.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 (138) hide show
  1. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/CHANGELOG.md +6 -0
  2. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/PKG-INFO +1 -1
  3. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/pyproject.toml +1 -1
  4. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/src/modern_python_guidance/__init__.py +1 -1
  5. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/src/modern_python_guidance/setup_cmd.py +10 -11
  6. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/tests/test_setup.py +39 -3
  7. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/tests/test_uninstall.py +21 -0
  8. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/.github/workflows/check-python-release.yml +0 -0
  9. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/.github/workflows/ci.yml +0 -0
  10. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/.github/workflows/publish.yml +0 -0
  11. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/.gitignore +0 -0
  12. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/CONTRIBUTING.md +0 -0
  13. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/LICENSE +0 -0
  14. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/LICENSE-MIT +0 -0
  15. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/README.md +0 -0
  16. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/SECURITY.md +0 -0
  17. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/edge-cases/opus48_multiline.py +0 -0
  18. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/edge-cases/valid_alt_patterns.py +0 -0
  19. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-a-modern/pyproject.toml +0 -0
  20. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-a-modern/src/app.py +0 -0
  21. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-a-modern/src/config.py +0 -0
  22. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-a-modern/src/crawler.py +0 -0
  23. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-a-modern/src/models.py +0 -0
  24. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-a-modern/src/scanner.py +0 -0
  25. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-a-modern/src/utils.py +0 -0
  26. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-a-outdated/pyproject.toml +0 -0
  27. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-a-outdated/setup.py +0 -0
  28. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-a-outdated/src/app.py +0 -0
  29. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-a-outdated/src/config.py +0 -0
  30. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-a-outdated/src/crawler.py +0 -0
  31. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-a-outdated/src/models.py +0 -0
  32. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-a-outdated/src/scanner.py +0 -0
  33. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-a-outdated/src/utils.py +0 -0
  34. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-b-modern/myapp/models.py +0 -0
  35. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-b-modern/myapp/views.py +0 -0
  36. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-b-outdated/myapp/models.py +0 -0
  37. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-b-outdated/myapp/views.py +0 -0
  38. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-c-modern/tests/test_calculator.py +0 -0
  39. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/fixtures/variant-c-outdated/tests/test_calculator.py +0 -0
  40. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/mcp-config.json +0 -0
  41. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/prompt-v2.txt +0 -0
  42. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/prompt-v3-mcp.txt +0 -0
  43. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/prompt-v3.txt +0 -0
  44. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/prompt-v4-a.txt +0 -0
  45. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/prompt-v4-b.txt +0 -0
  46. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/prompt-v4-c.txt +0 -0
  47. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/prompt.txt +0 -0
  48. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/prompts/v5-a-detailed.txt +0 -0
  49. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/prompts/v5-a-normal.txt +0 -0
  50. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/prompts/v5-a-terse.txt +0 -0
  51. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/prompts/v5-b-detailed.txt +0 -0
  52. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/prompts/v5-b-normal.txt +0 -0
  53. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/prompts/v5-b-terse.txt +0 -0
  54. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/prompts/v5-c-detailed.txt +0 -0
  55. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/prompts/v5-c-normal.txt +0 -0
  56. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/prompts/v5-c-terse.txt +0 -0
  57. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/run-mcp.sh +0 -0
  58. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/run-v4.sh +0 -0
  59. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/run-v5.sh +0 -0
  60. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/run.sh +0 -0
  61. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/score-v2.sh +0 -0
  62. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/score-v3.sh +0 -0
  63. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/score-v4.sh +0 -0
  64. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/score.sh +0 -0
  65. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/score_v5.py +0 -0
  66. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/bench/test-scorer.sh +0 -0
  67. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/docs/benchmark-evaluation.md +0 -0
  68. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/docs/benchmark-procedure.md +0 -0
  69. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/docs/benchmark-v5.md +0 -0
  70. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/docs/design.md +0 -0
  71. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/rules/modern-python.md +0 -0
  72. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/SKILL.md +0 -0
  73. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/async/async-timeout-context.md +0 -0
  74. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/async/exception-groups.md +0 -0
  75. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/async/taskgroup-over-gather.md +0 -0
  76. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/data-structures/dataclass-modern.md +0 -0
  77. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/data-structures/dict-merge-operator.md +0 -0
  78. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/data-structures/match-case-patterns.md +0 -0
  79. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/django/django-async-views.md +0 -0
  80. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/django/django-check-constraints.md +0 -0
  81. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/django/django-json-field.md +0 -0
  82. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/fastapi/fastapi-annotated-depends.md +0 -0
  83. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/fastapi/fastapi-lifespan.md +0 -0
  84. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/fastapi/fastapi-typed-state.md +0 -0
  85. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/httpx/httpx-async-client-reuse.md +0 -0
  86. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/httpx/httpx-streaming.md +0 -0
  87. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-config.md +0 -0
  88. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-model-api.md +0 -0
  89. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-serialization.md +0 -0
  90. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/pydantic/pydantic-v2-validators.md +0 -0
  91. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/pytest/pytest-parametrize.md +0 -0
  92. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/pytest/pytest-raises-match.md +0 -0
  93. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/pytest/pytest-tmp-path.md +0 -0
  94. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-2-style.md +0 -0
  95. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-async-session.md +0 -0
  96. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/sqlalchemy/sqlalchemy-mapped-column.md +0 -0
  97. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/stdlib/datetime-utc.md +0 -0
  98. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/stdlib/pathlib-over-os-path.md +0 -0
  99. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/stdlib/removeprefix-removesuffix.md +0 -0
  100. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/stdlib/template-strings.md +0 -0
  101. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/stdlib/tomllib-builtin.md +0 -0
  102. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/toolchain/no-pickle.md +0 -0
  103. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/toolchain/pyproject-toml-over-setup.md +0 -0
  104. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/toolchain/ruff-over-flake8.md +0 -0
  105. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/toolchain/safe-subprocess.md +0 -0
  106. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/toolchain/uv-over-pip.md +0 -0
  107. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/typing/deferred-annotations.md +0 -0
  108. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/typing/override-decorator.md +0 -0
  109. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/typing/paramspec-decorators.md +0 -0
  110. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/typing/type-parameter-syntax.md +0 -0
  111. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/typing/typeis-vs-typeguard.md +0 -0
  112. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/typing/union-syntax.md +0 -0
  113. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/skills/modern-python-guidance/guides/typing/use-builtin-generics.md +0 -0
  114. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/src/modern_python_guidance/__main__.py +0 -0
  115. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/src/modern_python_guidance/check.py +0 -0
  116. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/src/modern_python_guidance/cli.py +0 -0
  117. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/src/modern_python_guidance/compat.py +0 -0
  118. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/src/modern_python_guidance/frontmatter.py +0 -0
  119. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/src/modern_python_guidance/guide_index.py +0 -0
  120. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/src/modern_python_guidance/mcp_server.py +0 -0
  121. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/src/modern_python_guidance/retrieve.py +0 -0
  122. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/src/modern_python_guidance/search.py +0 -0
  123. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/src/modern_python_guidance/uninstall_cmd.py +0 -0
  124. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/src/modern_python_guidance/version_detect.py +0 -0
  125. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/tests/test_check.py +0 -0
  126. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/tests/test_cli_integration.py +0 -0
  127. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/tests/test_cli_unit.py +0 -0
  128. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/tests/test_compat.py +0 -0
  129. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/tests/test_frontmatter.py +0 -0
  130. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/tests/test_guide_index.py +0 -0
  131. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/tests/test_guide_structure.py +0 -0
  132. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/tests/test_mcp_server.py +0 -0
  133. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/tests/test_mcp_unit.py +0 -0
  134. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/tests/test_retrieve.py +0 -0
  135. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/tests/test_scorer_v5.py +0 -0
  136. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/tests/test_search.py +0 -0
  137. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/tests/test_skill_sync.py +0 -0
  138. {modern_python_guidance-0.4.1 → modern_python_guidance-0.4.2}/tests/test_version_detect.py +0 -0
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.4.2] — 2026-06-04
6
+
7
+ ### Fixed
8
+
9
+ - `_find_project_root()` escaping to `$HOME` when `~/.claude/` exists but the repo has no `.claude/` directory. Marker search is now per-level (nearest ancestor with any marker wins) instead of per-marker-type. If you previously ran `mpg setup` and have stale symlinks at `~/.claude/skills/modern-python-guidance` or `~/.claude/rules/modern-python.md`, remove them manually. (closes #90)
10
+
5
11
  ## [0.4.1] — 2026-06-03
6
12
 
7
13
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modern-python-guidance
3
- Version: 0.4.1
3
+ Version: 0.4.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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "modern-python-guidance"
7
- version = "0.4.1"
7
+ version = "0.4.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"
@@ -1,3 +1,3 @@
1
1
  """Modern Python Guidance — version-aware BAD/GOOD pattern guides for AI coding agents."""
2
2
 
3
- __version__ = "0.4.1"
3
+ __version__ = "0.4.2"
@@ -61,20 +61,19 @@ def _find_rule_source() -> Path:
61
61
 
62
62
 
63
63
  def _find_project_root(start: Path | None = None) -> Path:
64
- """Walk upward from *start* to find the project root."""
64
+ """Walk upward; return the nearest ancestor containing any marker."""
65
65
  current = (start or Path.cwd()).resolve()
66
- markers = [".claude", ".git", "pyproject.toml"]
66
+ markers = [".git", "pyproject.toml", ".claude"]
67
67
 
68
- for marker in markers:
69
- d = current
70
- while True:
71
- candidate = d / marker
72
- if candidate.exists():
68
+ d = current
69
+ while True:
70
+ for marker in markers:
71
+ if (d / marker).exists():
73
72
  return d
74
- parent = d.parent
75
- if parent == d:
76
- break
77
- d = parent
73
+ parent = d.parent
74
+ if parent == d:
75
+ break
76
+ d = parent
78
77
 
79
78
  return current
80
79
 
@@ -59,13 +59,31 @@ class TestFindProjectRoot:
59
59
  sub.mkdir(parents=True)
60
60
  assert _find_project_root(sub) == tmp_path
61
61
 
62
- def test_claude_takes_priority_over_git(self, tmp_path: Path):
63
- """.claude/ at parent wins over .git/ at child (higher-priority marker)."""
62
+ def test_nearest_marker_wins(self, tmp_path: Path):
63
+ """Nearest ancestor with any marker wins over distant ancestor."""
64
64
  (tmp_path / ".claude").mkdir()
65
65
  child = tmp_path / "child"
66
66
  child.mkdir()
67
67
  (child / ".git").mkdir()
68
- assert _find_project_root(child) == tmp_path
68
+ assert _find_project_root(child) == child
69
+
70
+ def test_bug_repro_home_claude_escapes(self, tmp_path: Path):
71
+ """V-001: .claude at home + .git at repo -> repo wins, not home."""
72
+ home = tmp_path / "home"
73
+ (home / ".claude").mkdir(parents=True)
74
+ repo = home / "projects" / "repo"
75
+ repo.mkdir(parents=True)
76
+ (repo / ".git").mkdir()
77
+ src = repo / "src"
78
+ src.mkdir()
79
+ assert _find_project_root(src) == repo
80
+
81
+ def test_git_as_file(self, tmp_path: Path):
82
+ """V-008: .git as a file (worktree/submodule) is detected."""
83
+ (tmp_path / ".git").write_text("gitdir: /some/other/path")
84
+ sub = tmp_path / "src"
85
+ sub.mkdir()
86
+ assert _find_project_root(sub) == tmp_path
69
87
 
70
88
  def test_falls_back_to_cwd(self, tmp_path: Path):
71
89
  bare = tmp_path / "empty"
@@ -308,6 +326,24 @@ class TestSetupSkills:
308
326
  err = capsys.readouterr().err
309
327
  assert "'" in err or '"' in err
310
328
 
329
+ def test_autodetect_avoids_home_escape(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
330
+ """setup_skills auto-discovers repo root, not distant ~/.claude."""
331
+ source = self._make_source(tmp_path)
332
+ home = tmp_path / "home"
333
+ (home / ".claude").mkdir(parents=True)
334
+ repo = home / "projects" / "repo"
335
+ repo.mkdir(parents=True)
336
+ (repo / ".git").mkdir()
337
+
338
+ monkeypatch.chdir(repo)
339
+ with patch("modern_python_guidance.setup_cmd._find_skills_dir", return_value=source):
340
+ ok = setup_skills()
341
+
342
+ assert ok is True
343
+ link = repo / ".claude" / "skills" / "modern-python-guidance"
344
+ assert link.is_symlink()
345
+ assert not (home / ".claude" / "skills" / "modern-python-guidance").exists()
346
+
311
347
  def test_dry_run(self, tmp_path: Path, capsys: pytest.CaptureFixture[str]):
312
348
  """V-010: dry-run does not create symlink."""
313
349
  source = self._make_source(tmp_path)
@@ -248,6 +248,27 @@ class TestUninstallSkills:
248
248
  assert link.is_symlink() # still there
249
249
  assert "Would remove" in capsys.readouterr().out
250
250
 
251
+ def test_resolves_correct_root_not_distant_ancestor(
252
+ self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
253
+ ):
254
+ """V-009: uninstall resolves nearest project root, not distant ancestor."""
255
+ home = tmp_path / "home"
256
+ (home / ".claude" / "skills" / "modern-python-guidance").mkdir(parents=True)
257
+ repo = home / "projects" / "repo"
258
+ repo.mkdir(parents=True)
259
+ (repo / ".git").mkdir()
260
+ link = repo / ".claude" / "skills" / "modern-python-guidance"
261
+ link.parent.mkdir(parents=True)
262
+ os.symlink(tmp_path / "pkg_skills", link)
263
+
264
+ monkeypatch.chdir(repo)
265
+ ok = uninstall_skills()
266
+
267
+ assert ok is True
268
+ assert not link.is_symlink()
269
+ # Home-level skills dir must be untouched
270
+ assert (home / ".claude" / "skills" / "modern-python-guidance").is_dir()
271
+
251
272
  def test_non_symlink_path_quoted(self, tmp_path: Path, capsys):
252
273
  """V-028: rm hint uses shell-safe quoting for paths with spaces."""
253
274
  project = tmp_path / "my project"