python-skills 1.0.0__py3-none-any.whl

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 (105) hide show
  1. python_skills/__init__.py +10 -0
  2. python_skills/__main__.py +6 -0
  3. python_skills/adapters/__init__.py +48 -0
  4. python_skills/adapters/agent_skills.py +415 -0
  5. python_skills/adapters/aider_adapter.py +226 -0
  6. python_skills/adapters/base.py +153 -0
  7. python_skills/adapters/claude.py +474 -0
  8. python_skills/adapters/cline.py +332 -0
  9. python_skills/adapters/codex.py +24 -0
  10. python_skills/adapters/continue_adapter.py +198 -0
  11. python_skills/adapters/cursor.py +327 -0
  12. python_skills/adapters/gemini.py +26 -0
  13. python_skills/adapters/goose.py +26 -0
  14. python_skills/adapters/junie.py +25 -0
  15. python_skills/adapters/kiro.py +382 -0
  16. python_skills/adapters/opencode.py +27 -0
  17. python_skills/adapters/roo.py +25 -0
  18. python_skills/adapters/universal.py +203 -0
  19. python_skills/adapters/vscode.py +27 -0
  20. python_skills/adapters/windsurf.py +26 -0
  21. python_skills/adapters/zed.py +27 -0
  22. python_skills/cli.py +326 -0
  23. python_skills/config.py +160 -0
  24. python_skills/detector.py +152 -0
  25. python_skills/installer.py +163 -0
  26. python_skills/markers.py +115 -0
  27. python_skills/skills/__init__.py +14 -0
  28. python_skills/skills/loader.py +171 -0
  29. python_skills/skills/metadata.py +152 -0
  30. python_skills/skills/registry.py +101 -0
  31. python_skills/state.py +204 -0
  32. python_skills-1.0.0.dist-info/METADATA +99 -0
  33. python_skills-1.0.0.dist-info/RECORD +105 -0
  34. python_skills-1.0.0.dist-info/WHEEL +4 -0
  35. python_skills-1.0.0.dist-info/entry_points.txt +2 -0
  36. python_skills-1.0.0.dist-info/licenses/LICENSE +21 -0
  37. skills/advanced_python.md +239 -0
  38. skills/anti_patterns/index.md +406 -0
  39. skills/comprehensions.md +167 -0
  40. skills/control_flow.md +175 -0
  41. skills/data_structures.md +243 -0
  42. skills/debugging/common_bugs.md +222 -0
  43. skills/debugging/inspection_techniques.md +249 -0
  44. skills/debugging/root_cause.md +203 -0
  45. skills/engineering/application_logging.md +195 -0
  46. skills/engineering/cli_apps.md +207 -0
  47. skills/engineering/configuration.md +218 -0
  48. skills/engineering/database.md +240 -0
  49. skills/engineering/dependency_management.md +205 -0
  50. skills/engineering/http_clients.md +267 -0
  51. skills/engineering/modules_packages.md +211 -0
  52. skills/engineering/packaging.md +197 -0
  53. skills/engineering/project_structure.md +155 -0
  54. skills/engineering/pyproject_toml.md +302 -0
  55. skills/engineering/virtual_environments.md +206 -0
  56. skills/functions.md +244 -0
  57. skills/generation/async_concurrency.md +291 -0
  58. skills/generation/error_handling.md +276 -0
  59. skills/generation/protocols_generics.md +243 -0
  60. skills/generation/type_hints.md +290 -0
  61. skills/generation/validation_pipeline.md +274 -0
  62. skills/generation/workflow.md +190 -0
  63. skills/oop.md +228 -0
  64. skills/quality/abstractions.md +154 -0
  65. skills/quality/comments.md +177 -0
  66. skills/quality/documentation.md +176 -0
  67. skills/quality/duplication.md +137 -0
  68. skills/quality/maintainability.md +142 -0
  69. skills/quality/naming.md +171 -0
  70. skills/quality/quality_functions.md +245 -0
  71. skills/quality/readability.md +239 -0
  72. skills/quality/type_annotations.md +192 -0
  73. skills/refactoring/behavior_preservation.md +157 -0
  74. skills/refactoring/incremental.md +187 -0
  75. skills/refactoring/interface_stability.md +199 -0
  76. skills/refactoring/safe_refactoring.md +206 -0
  77. skills/security/auth_boundaries.md +200 -0
  78. skills/security/command_injection.md +207 -0
  79. skills/security/dependency_risks.md +282 -0
  80. skills/security/file_handling.md +156 -0
  81. skills/security/input_validation.md +190 -0
  82. skills/security/path_traversal.md +172 -0
  83. skills/security/secrets.md +171 -0
  84. skills/security/sql_injection.md +188 -0
  85. skills/security/unsafe_deserialization.md +164 -0
  86. skills/stdlib/argparse.md +178 -0
  87. skills/stdlib/collections.md +212 -0
  88. skills/stdlib/datetime.md +187 -0
  89. skills/stdlib/functools.md +238 -0
  90. skills/stdlib/itertools.md +183 -0
  91. skills/stdlib/json.md +162 -0
  92. skills/stdlib/logging.md +185 -0
  93. skills/stdlib/os_sys.md +184 -0
  94. skills/stdlib/pathlib.md +218 -0
  95. skills/stdlib/re.md +171 -0
  96. skills/stdlib/statistics.md +112 -0
  97. skills/stdlib/subprocess.md +211 -0
  98. skills/testing/async_tests.md +249 -0
  99. skills/testing/coverage.md +168 -0
  100. skills/testing/edge_cases.md +197 -0
  101. skills/testing/fixtures_mocks.md +203 -0
  102. skills/testing/organization.md +205 -0
  103. skills/testing/parameterized.md +174 -0
  104. skills/testing/regression_tests.md +165 -0
  105. skills/variables_types.md +107 -0
@@ -0,0 +1,327 @@
1
+ """Cursor adapter for python-skills."""
2
+
3
+ import shutil
4
+ from pathlib import Path
5
+
6
+ from ..adapters.base import (
7
+ AgentAdapter,
8
+ DetectionResult,
9
+ InstallResult,
10
+ StatusResult,
11
+ SyncResult,
12
+ UninstallResult,
13
+ )
14
+ from ..config import Target
15
+ from ..markers import BEGIN_MARKER, END_MARKER
16
+ from ..skills.registry import SkillRegistry
17
+ from ..state import LockManager
18
+
19
+
20
+ class CursorAdapter(AgentAdapter):
21
+ """Adapter for Cursor."""
22
+
23
+ name = "Cursor"
24
+ target = Target.CURSOR.value
25
+
26
+ def __init__(self, project_root: Path, skills_registry: SkillRegistry, lock_manager: LockManager, config):
27
+ super().__init__(project_root, skills_registry, lock_manager, config)
28
+
29
+ def _get_global_path(self) -> Path:
30
+ """Cursor global rules are stored in VS Code settings, not filesystem."""
31
+ return Path.home() / ".cursor" # Not actually used
32
+
33
+ def detect(self) -> DetectionResult:
34
+ """Detect Cursor installation and configuration."""
35
+ # Check if cursor is in PATH
36
+ app_detected = shutil.which("cursor") is not None
37
+
38
+ # Check for project config
39
+ project_config = (self.project_root / ".cursor").exists() or (self.project_root / ".cursorrules").exists()
40
+
41
+ return DetectionResult(
42
+ application_detected=app_detected,
43
+ project_config_detected=project_config,
44
+ adapter_available=True,
45
+ details=f"App: {'yes' if app_detected else 'no'}, Project config: {'yes' if project_config else 'no'}, Global: N/A (UI only)"
46
+ )
47
+
48
+ def install(self, scope: str = "project", dry_run: bool = False) -> InstallResult:
49
+ """Install python-skills for Cursor."""
50
+ result = InstallResult(success=True, target=self.target, scope=scope)
51
+ target_path = self._get_scope_path(scope)
52
+
53
+ if scope == "global":
54
+ result.success = False
55
+ result.errors.append("Cursor global installation not supported (User Rules are UI-only)")
56
+ return result
57
+
58
+ try:
59
+ # Generate .cursor/rules/python-skills.mdc
60
+ rules_dir = target_path / ".cursor" / "rules"
61
+ rules_dir.mkdir(parents=True, exist_ok=True)
62
+
63
+ rule_path = rules_dir / "python-skills.mdc"
64
+ content = self._generate_cursor_rule()
65
+
66
+ if dry_run:
67
+ result.files_created.append(str(rule_path.relative_to(self.project_root)))
68
+ else:
69
+ self._safe_write_file(rule_path, content, dry_run)
70
+ result.files_created.append(str(rule_path.relative_to(self.project_root)))
71
+ self._record_file(self.target, scope, rule_path)
72
+
73
+ # Also generate AGENTS.md bootstrap
74
+ agents_md = self._generate_agents_md(target_path, dry_run)
75
+ if agents_md:
76
+ result.files_created.append(agents_md)
77
+
78
+ if not dry_run:
79
+ self._update_lock_state(scope)
80
+
81
+ except Exception as e:
82
+ result.success = False
83
+ result.errors.append(str(e))
84
+
85
+ return result
86
+
87
+ def _generate_cursor_rule(self) -> str:
88
+ """Generate Cursor rule with python-skills integration."""
89
+ # Get all skill names for reference
90
+ categories = {}
91
+ for skill in self.skills_registry.get_all_skills():
92
+ if skill.category not in categories:
93
+ categories[skill.category] = []
94
+ categories[skill.category].append(skill.name)
95
+
96
+ rule_parts = [
97
+ "---",
98
+ 'description: "Python engineering skills and patterns"',
99
+ 'globs: ["**/*.py"]',
100
+ "alwaysApply: false",
101
+ "---",
102
+ "",
103
+ "<!-- BEGIN PYTHON-SKILLS MANAGED -->",
104
+ "",
105
+ "# Python Skills Integration",
106
+ "",
107
+ "This project uses [python-skills](https://github.com/FoxPink-dev/python-skills) for Python engineering standards.",
108
+ "",
109
+ "## Available Skill Categories",
110
+ "",
111
+ ]
112
+
113
+ for category, skills in sorted(categories.items()):
114
+ category_parts = [f"### {category.title()}", ""]
115
+ for skill in sorted(skills):
116
+ category_parts.append(f"- `{skill}`")
117
+ category_parts.append("")
118
+ rule_parts.extend(category_parts)
119
+
120
+ rule_parts.extend([
121
+ "## Usage",
122
+ "",
123
+ "Reference relevant skills when working on Python code. Key patterns:",
124
+ "",
125
+ "- **Type hints**: Use built-in generics, `str | None`, `Protocol`",
126
+ "- **Async**: `asyncio.gather`, `Semaphore`, `TaskGroup` (3.11+)",
127
+ "- **HTTP**: `httpx` with retries, timeouts, connection pooling",
128
+ "- **Security**: Parameterized queries, `pathlib` for paths, `yaml.safe_load`",
129
+ "- **Testing**: `pytest` with parametrized tests, edge cases",
130
+ "- **CLI**: `click`/`typer`, config layering, secrets via env",
131
+ "",
132
+ "Reference: [python-skills repository](https://github.com/FoxPink-dev/python-skills)",
133
+ "",
134
+ "<!-- END PYTHON-SKILLS MANAGED -->",
135
+ ])
136
+
137
+ return "\n".join(rule_parts)
138
+
139
+ def _generate_agents_md(self, target_path: Path, dry_run: bool) -> str | None:
140
+ agents_md = target_path / "AGENTS.md"
141
+ bootstrap = self._generate_agents_md_content()
142
+
143
+ if dry_run:
144
+ return str(agents_md.relative_to(self.project_root))
145
+
146
+ if agents_md.exists():
147
+ existing = agents_md.read_text(encoding="utf-8")
148
+ new_content, replaced = self._replace_managed_region(existing, bootstrap, agents_md)
149
+ if replaced:
150
+ self._safe_write_file(agents_md, new_content, dry_run)
151
+ self._record_file(self.target, "project", agents_md, (BEGIN_MARKER, END_MARKER))
152
+ else:
153
+ new_content = existing + "\n\n" + self._wrap_managed(bootstrap, agents_md)
154
+ self._safe_write_file(agents_md, new_content, dry_run)
155
+ self._record_file(self.target, "project", agents_md, (BEGIN_MARKER, END_MARKER))
156
+ else:
157
+ self._safe_write_file(agents_md, self._wrap_managed(bootstrap, agents_md), dry_run)
158
+ self._record_file(self.target, "project", agents_md, (BEGIN_MARKER, END_MARKER))
159
+
160
+ return str(agents_md.relative_to(self.project_root))
161
+
162
+ def _generate_agents_md_content(self) -> str:
163
+ return """<!-- BEGIN PYTHON-SKILLS MANAGED -->
164
+ # Python Skills Integration
165
+
166
+ This project uses [python-skills](https://github.com/FoxPink-dev/python-skills) for Python engineering standards.
167
+
168
+ ## Available Skills
169
+
170
+ The canonical skill library is at `python-skills/skills/` with 69 skills across 10 categories.
171
+
172
+ ### Core
173
+ - variables_types, control_flow, functions, data_structures, oop, comprehensions, advanced_python
174
+
175
+ ### Stdlib
176
+ - argparse, collections, datetime, functools, itertools, json, logging, os_sys, pathlib, re, statistics, subprocess
177
+
178
+ ### Generation
179
+ - type_hints, protocols_generics, async_concurrency, error_handling, validation_pipeline, workflow
180
+
181
+ ### Engineering
182
+ - cli_apps, configuration, database, dependency_management, http_clients, logging, modules_packages, packaging, project_structure, pyproject_toml, virtual_environments
183
+
184
+ ### Quality
185
+ - abstractions, comments, documentation, duplication, functions, maintainability, naming, readability, type_annotations
186
+
187
+ ### Security
188
+ - auth_boundaries, command_injection, dependency_risks, file_handling, input_validation, path_traversal, secrets, sql_injection, unsafe_deserialization
189
+
190
+ ### Testing
191
+ - async_tests, coverage, edge_cases, fixtures_mocks, organization, parameterized, regression_tests
192
+
193
+ ### Refactoring
194
+ - behavior_preservation, incremental, interface_stability, safe_refactoring
195
+
196
+ ### Debugging
197
+ - common_bugs, inspection_techniques, root_cause
198
+
199
+ ### Anti-Patterns
200
+ - index
201
+
202
+ ## Usage
203
+ Reference relevant skills when working on Python code. Skills are loaded on-demand based on task context.
204
+ <!-- END PYTHON-SKILLS MANAGED -->"""
205
+
206
+ def sync(self, scope: str = "project", dry_run: bool = False) -> SyncResult:
207
+ result = SyncResult(success=True, target=self.target, scope=scope)
208
+
209
+ if scope == "global":
210
+ result.success = False
211
+ result.errors.append("Cursor global scope not supported")
212
+ return result
213
+
214
+ try:
215
+ target_path = self._get_scope_path(scope)
216
+ rules_dir = target_path / ".cursor" / "rules"
217
+
218
+ if not rules_dir.exists():
219
+ result.success = False
220
+ result.errors.append("No .cursor/rules directory found")
221
+ return result
222
+
223
+ rule_path = rules_dir / "python-skills.mdc"
224
+
225
+ if not rule_path.exists():
226
+ result.success = False
227
+ result.errors.append("python-skills.mdc not found")
228
+ return result
229
+
230
+ # Read current rule
231
+ current = rule_path.read_text(encoding="utf-8")
232
+
233
+ # Generate new content
234
+ new_content = self._generate_cursor_rule()
235
+
236
+ # Check if different
237
+ if current.strip() != new_content.strip():
238
+ if not dry_run:
239
+ self._safe_write_file(rule_path, new_content, dry_run)
240
+ result.modified.append(str(rule_path.relative_to(self.project_root)))
241
+ else:
242
+ result.skipped.append(str(rule_path.relative_to(self.project_root)))
243
+
244
+ # Update AGENTS.md
245
+ agents_md = target_path / "AGENTS.md"
246
+ if agents_md.exists():
247
+ current = agents_md.read_text(encoding="utf-8")
248
+ new_agents = self._generate_agents_md_content()
249
+ new_content, replaced = self._replace_managed_region(current, new_agents, agents_md)
250
+ if replaced and not dry_run:
251
+ self._safe_write_file(agents_md, new_content, dry_run)
252
+ result.modified.append(str(agents_md.relative_to(self.project_root)))
253
+
254
+ if not dry_run:
255
+ self._update_lock_state(scope)
256
+
257
+ except Exception as e:
258
+ result.success = False
259
+ result.errors.append(str(e))
260
+
261
+ return result
262
+
263
+ def uninstall(self, scope: str = "project", dry_run: bool = False) -> UninstallResult:
264
+ result = UninstallResult(success=True, target=self.target, scope=scope)
265
+
266
+ if scope == "global":
267
+ result.success = False
268
+ result.errors.append("Cursor global scope not supported")
269
+ return result
270
+
271
+ try:
272
+ target_path = self._get_scope_path(scope)
273
+ rules_dir = target_path / ".cursor" / "rules"
274
+ rule_path = rules_dir / "python-skills.mdc"
275
+
276
+ if rule_path.exists() and self._has_ownership_marker(rule_path):
277
+ if not dry_run:
278
+ rule_path.unlink()
279
+ result.files_removed.append(str(rule_path.relative_to(self.project_root)))
280
+
281
+ # Clean AGENTS.md
282
+ agents_md = target_path / "AGENTS.md"
283
+ if agents_md.exists():
284
+ content = agents_md.read_text(encoding="utf-8")
285
+ new_content, removed = self._remove_managed_region(content, agents_md)
286
+ if removed and not dry_run:
287
+ self._safe_write_file(agents_md, new_content, dry_run)
288
+ result.regions_removed.append(str(agents_md.relative_to(self.project_root)))
289
+
290
+ if not dry_run:
291
+ self._update_lock_state(scope)
292
+
293
+ except Exception as e:
294
+ result.success = False
295
+ result.errors.append(str(e))
296
+
297
+ return result
298
+
299
+ def _has_ownership_marker(self, path: Path) -> bool:
300
+ if not path.exists():
301
+ return False
302
+ content = path.read_text(encoding="utf-8")
303
+ begin, end = self._get_markers(path)
304
+ return begin in content and end in content
305
+
306
+ def status(self, scope: str = "project") -> StatusResult:
307
+ target_path = self._get_scope_path(scope)
308
+ installed = False
309
+ files = []
310
+
311
+ if scope == "global":
312
+ return StatusResult(target=self.target, scope=scope, installed=False, files=[], version="1.0.0")
313
+
314
+ rule_path = target_path / ".cursor" / "rules" / "python-skills.mdc"
315
+ if rule_path.exists() and self._has_ownership_marker(rule_path):
316
+ installed = True
317
+ files.append(str(rule_path.relative_to(self.project_root)))
318
+
319
+ agents_md = target_path / "AGENTS.md"
320
+ if agents_md.exists() and self._has_ownership_marker(agents_md):
321
+ installed = True
322
+ files.append(str(agents_md.relative_to(self.project_root)))
323
+
324
+ return StatusResult(target=self.target, scope=scope, installed=installed, files=files, version="1.0.0")
325
+
326
+ def _update_lock_state(self, scope: str) -> None:
327
+ self.lock_manager.update_canonical_hash(self.skills_registry.skills_root)
@@ -0,0 +1,26 @@
1
+ """Gemini CLI adapter for python-skills."""
2
+
3
+ from pathlib import Path
4
+
5
+ from .agent_skills import AgentSkillsAdapter
6
+
7
+
8
+ class GeminiAdapter(AgentSkillsAdapter):
9
+ """Adapter for Google Gemini CLI.
10
+
11
+ Gemini CLI discovers skills from:
12
+ - .gemini/skills/<name>/SKILL.md (native)
13
+ - .agents/skills/<name>/SKILL.md (cross-agent standard)
14
+
15
+ We install to .agents/skills/ (shared) and .gemini/skills/ (native).
16
+ """
17
+
18
+ target_name = "gemini"
19
+ display_name = "Gemini CLI"
20
+ agent_skills_dir = ".agents/skills"
21
+ native_skills_dir = ".gemini/skills"
22
+ detection_app_command = "gemini"
23
+ detection_project_markers = [".gemini", "GEMINI.md"]
24
+
25
+ def _get_global_path(self) -> Path:
26
+ return Path.home() / ".gemini" / "skills"
@@ -0,0 +1,26 @@
1
+ """Goose adapter for python-skills."""
2
+
3
+ from pathlib import Path
4
+
5
+ from .agent_skills import AgentSkillsAdapter
6
+
7
+
8
+ class GooseAdapter(AgentSkillsAdapter):
9
+ """Adapter for Goose (by Block/Square).
10
+
11
+ Goose discovers skills from:
12
+ - .agents/skills/<name>/SKILL.md (cross-agent standard)
13
+ - .goose/skills/<name>/SKILL.md (backward compat)
14
+ - .claude/skills/<name>/SKILL.md (backward compat)
15
+
16
+ We install to .agents/skills/ (shared).
17
+ """
18
+
19
+ target_name = "goose"
20
+ display_name = "Goose"
21
+ agent_skills_dir = ".agents/skills"
22
+ detection_app_command = "goose"
23
+ detection_project_markers = [".goosehints", "AGENTS.md"]
24
+
25
+ def _get_global_path(self) -> Path:
26
+ return Path.home() / ".config" / "goose" / "skills"
@@ -0,0 +1,25 @@
1
+ """JetBrains / Junie adapter for python-skills."""
2
+
3
+ from pathlib import Path
4
+
5
+ from .agent_skills import AgentSkillsAdapter
6
+
7
+
8
+ class JunieAdapter(AgentSkillsAdapter):
9
+ """Adapter for JetBrains AI / Junie.
10
+
11
+ Junie discovers skills from:
12
+ - .junie/skills/<name>/SKILL.md (native)
13
+ - .agents/skills/<name>/SKILL.md (cross-agent standard)
14
+
15
+ We install to .agents/skills/ (shared) and .junie/skills/ (native).
16
+ """
17
+
18
+ target_name = "jetbrains"
19
+ display_name = "JetBrains / Junie"
20
+ agent_skills_dir = ".agents/skills"
21
+ native_skills_dir = ".junie/skills"
22
+ detection_project_markers = [".junie"]
23
+
24
+ def _get_global_path(self) -> Path:
25
+ return Path.home() / ".junie" / "skills"