devflow-kit 1.0.0 → 1.2.0

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 (134) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +35 -11
  3. package/dist/cli.js +5 -1
  4. package/dist/commands/ambient.d.ts +18 -0
  5. package/dist/commands/ambient.js +136 -0
  6. package/dist/commands/init.d.ts +2 -0
  7. package/dist/commands/init.js +97 -10
  8. package/dist/commands/memory.d.ts +22 -0
  9. package/dist/commands/memory.js +175 -0
  10. package/dist/commands/uninstall.js +72 -5
  11. package/dist/plugins.js +74 -3
  12. package/dist/utils/post-install.d.ts +12 -0
  13. package/dist/utils/post-install.js +82 -1
  14. package/dist/utils/safe-delete-install.d.ts +7 -0
  15. package/dist/utils/safe-delete-install.js +40 -5
  16. package/package.json +2 -1
  17. package/plugins/devflow-accessibility/.claude-plugin/plugin.json +15 -0
  18. package/plugins/devflow-ambient/.claude-plugin/plugin.json +7 -0
  19. package/plugins/devflow-ambient/README.md +49 -0
  20. package/plugins/devflow-ambient/commands/ambient.md +110 -0
  21. package/plugins/devflow-ambient/skills/ambient-router/SKILL.md +89 -0
  22. package/plugins/devflow-ambient/skills/ambient-router/references/skill-catalog.md +68 -0
  23. package/plugins/devflow-audit-claude/.claude-plugin/plugin.json +1 -1
  24. package/plugins/devflow-code-review/.claude-plugin/plugin.json +1 -4
  25. package/plugins/devflow-code-review/agents/reviewer.md +8 -0
  26. package/plugins/devflow-code-review/commands/code-review-teams.md +11 -1
  27. package/plugins/devflow-code-review/commands/code-review.md +12 -2
  28. package/plugins/devflow-core-skills/.claude-plugin/plugin.json +3 -6
  29. package/plugins/devflow-core-skills/skills/docs-framework/SKILL.md +10 -6
  30. package/plugins/devflow-core-skills/skills/test-driven-development/SKILL.md +139 -0
  31. package/plugins/devflow-core-skills/skills/test-driven-development/references/rationalization-prevention.md +111 -0
  32. package/plugins/devflow-debug/.claude-plugin/plugin.json +1 -1
  33. package/plugins/devflow-frontend-design/.claude-plugin/plugin.json +15 -0
  34. package/plugins/devflow-go/.claude-plugin/plugin.json +15 -0
  35. package/plugins/devflow-go/skills/go/SKILL.md +187 -0
  36. package/plugins/devflow-go/skills/go/references/concurrency.md +312 -0
  37. package/plugins/devflow-go/skills/go/references/detection.md +129 -0
  38. package/plugins/devflow-go/skills/go/references/patterns.md +232 -0
  39. package/plugins/devflow-go/skills/go/references/violations.md +205 -0
  40. package/plugins/devflow-implement/.claude-plugin/plugin.json +1 -3
  41. package/plugins/devflow-implement/agents/coder.md +11 -6
  42. package/plugins/devflow-java/.claude-plugin/plugin.json +15 -0
  43. package/plugins/devflow-java/skills/java/SKILL.md +183 -0
  44. package/plugins/devflow-java/skills/java/references/detection.md +120 -0
  45. package/plugins/devflow-java/skills/java/references/modern-java.md +270 -0
  46. package/plugins/devflow-java/skills/java/references/patterns.md +235 -0
  47. package/plugins/devflow-java/skills/java/references/violations.md +213 -0
  48. package/plugins/devflow-python/.claude-plugin/plugin.json +15 -0
  49. package/plugins/devflow-python/skills/python/SKILL.md +188 -0
  50. package/plugins/devflow-python/skills/python/references/async.md +220 -0
  51. package/plugins/devflow-python/skills/python/references/detection.md +128 -0
  52. package/plugins/devflow-python/skills/python/references/patterns.md +226 -0
  53. package/plugins/devflow-python/skills/python/references/violations.md +204 -0
  54. package/plugins/devflow-react/.claude-plugin/plugin.json +15 -0
  55. package/plugins/{devflow-core-skills → devflow-react}/skills/react/SKILL.md +1 -1
  56. package/plugins/{devflow-core-skills → devflow-react}/skills/react/references/patterns.md +3 -3
  57. package/plugins/devflow-resolve/.claude-plugin/plugin.json +1 -1
  58. package/plugins/devflow-rust/.claude-plugin/plugin.json +15 -0
  59. package/plugins/devflow-rust/skills/rust/SKILL.md +193 -0
  60. package/plugins/devflow-rust/skills/rust/references/detection.md +131 -0
  61. package/plugins/devflow-rust/skills/rust/references/ownership.md +242 -0
  62. package/plugins/devflow-rust/skills/rust/references/patterns.md +210 -0
  63. package/plugins/devflow-rust/skills/rust/references/violations.md +191 -0
  64. package/plugins/devflow-self-review/.claude-plugin/plugin.json +1 -1
  65. package/plugins/devflow-specify/.claude-plugin/plugin.json +1 -1
  66. package/plugins/devflow-typescript/.claude-plugin/plugin.json +15 -0
  67. package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/references/patterns.md +3 -3
  68. package/scripts/hooks/ambient-prompt.sh +48 -0
  69. package/scripts/hooks/background-memory-update.sh +49 -8
  70. package/scripts/hooks/ensure-memory-gitignore.sh +17 -0
  71. package/scripts/hooks/pre-compact-memory.sh +12 -6
  72. package/scripts/hooks/session-start-memory.sh +50 -8
  73. package/scripts/hooks/stop-update-memory.sh +10 -6
  74. package/shared/agents/coder.md +11 -6
  75. package/shared/agents/reviewer.md +8 -0
  76. package/shared/skills/ambient-router/SKILL.md +89 -0
  77. package/shared/skills/ambient-router/references/skill-catalog.md +68 -0
  78. package/shared/skills/docs-framework/SKILL.md +10 -6
  79. package/shared/skills/go/SKILL.md +187 -0
  80. package/shared/skills/go/references/concurrency.md +312 -0
  81. package/shared/skills/go/references/detection.md +129 -0
  82. package/shared/skills/go/references/patterns.md +232 -0
  83. package/shared/skills/go/references/violations.md +205 -0
  84. package/shared/skills/java/SKILL.md +183 -0
  85. package/shared/skills/java/references/detection.md +120 -0
  86. package/shared/skills/java/references/modern-java.md +270 -0
  87. package/shared/skills/java/references/patterns.md +235 -0
  88. package/shared/skills/java/references/violations.md +213 -0
  89. package/shared/skills/python/SKILL.md +188 -0
  90. package/shared/skills/python/references/async.md +220 -0
  91. package/shared/skills/python/references/detection.md +128 -0
  92. package/shared/skills/python/references/patterns.md +226 -0
  93. package/shared/skills/python/references/violations.md +204 -0
  94. package/shared/skills/react/SKILL.md +1 -1
  95. package/shared/skills/react/references/patterns.md +3 -3
  96. package/shared/skills/rust/SKILL.md +193 -0
  97. package/shared/skills/rust/references/detection.md +131 -0
  98. package/shared/skills/rust/references/ownership.md +242 -0
  99. package/shared/skills/rust/references/patterns.md +210 -0
  100. package/shared/skills/rust/references/violations.md +191 -0
  101. package/shared/skills/test-driven-development/SKILL.md +139 -0
  102. package/shared/skills/test-driven-development/references/rationalization-prevention.md +111 -0
  103. package/shared/skills/typescript/references/patterns.md +3 -3
  104. package/src/templates/managed-settings.json +14 -0
  105. package/plugins/devflow-code-review/skills/react/SKILL.md +0 -276
  106. package/plugins/devflow-code-review/skills/react/references/patterns.md +0 -1331
  107. package/plugins/devflow-core-skills/skills/accessibility/SKILL.md +0 -229
  108. package/plugins/devflow-core-skills/skills/accessibility/references/detection.md +0 -171
  109. package/plugins/devflow-core-skills/skills/accessibility/references/patterns.md +0 -670
  110. package/plugins/devflow-core-skills/skills/accessibility/references/violations.md +0 -419
  111. package/plugins/devflow-core-skills/skills/frontend-design/SKILL.md +0 -254
  112. package/plugins/devflow-core-skills/skills/frontend-design/references/detection.md +0 -184
  113. package/plugins/devflow-core-skills/skills/frontend-design/references/patterns.md +0 -511
  114. package/plugins/devflow-core-skills/skills/frontend-design/references/violations.md +0 -453
  115. package/plugins/devflow-core-skills/skills/react/references/violations.md +0 -565
  116. package/plugins/devflow-implement/skills/accessibility/SKILL.md +0 -229
  117. package/plugins/devflow-implement/skills/accessibility/references/detection.md +0 -171
  118. package/plugins/devflow-implement/skills/accessibility/references/patterns.md +0 -670
  119. package/plugins/devflow-implement/skills/accessibility/references/violations.md +0 -419
  120. package/plugins/devflow-implement/skills/frontend-design/SKILL.md +0 -254
  121. package/plugins/devflow-implement/skills/frontend-design/references/detection.md +0 -184
  122. package/plugins/devflow-implement/skills/frontend-design/references/patterns.md +0 -511
  123. package/plugins/devflow-implement/skills/frontend-design/references/violations.md +0 -453
  124. /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/SKILL.md +0 -0
  125. /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/detection.md +0 -0
  126. /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/patterns.md +0 -0
  127. /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/violations.md +0 -0
  128. /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/SKILL.md +0 -0
  129. /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/detection.md +0 -0
  130. /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/patterns.md +0 -0
  131. /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/violations.md +0 -0
  132. /package/plugins/{devflow-code-review → devflow-react}/skills/react/references/violations.md +0 -0
  133. /package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/SKILL.md +0 -0
  134. /package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/references/violations.md +0 -0
@@ -0,0 +1,220 @@
1
+ # Async Python Patterns
2
+
3
+ Deep-dive on async Python programming. Reference from main SKILL.md.
4
+
5
+ ## Core asyncio Patterns
6
+
7
+ ### Basic Async Function
8
+
9
+ ```python
10
+ import asyncio
11
+ from typing import Any
12
+
13
+ async def fetch_user(user_id: str) -> User:
14
+ async with aiohttp.ClientSession() as session:
15
+ async with session.get(f"/api/users/{user_id}") as response:
16
+ data = await response.json()
17
+ return User.model_validate(data)
18
+ ```
19
+
20
+ ### Concurrent Execution with gather
21
+
22
+ ```python
23
+ async def load_dashboard(user_id: str) -> Dashboard:
24
+ # Independent fetches run concurrently
25
+ user, orders, preferences = await asyncio.gather(
26
+ fetch_user(user_id),
27
+ fetch_orders(user_id),
28
+ fetch_preferences(user_id),
29
+ )
30
+ return Dashboard(user=user, orders=orders, preferences=preferences)
31
+ ```
32
+
33
+ ## Structured Concurrency with TaskGroup
34
+
35
+ ### TaskGroup for Safe Concurrency (Python 3.11+)
36
+
37
+ ```python
38
+ async def process_batch(items: list[Item]) -> list[Result]:
39
+ results: list[Result] = []
40
+
41
+ async with asyncio.TaskGroup() as tg:
42
+ for item in items:
43
+ tg.create_task(process_and_collect(item, results))
44
+
45
+ return results
46
+
47
+ async def process_and_collect(item: Item, results: list[Result]) -> None:
48
+ result = await process_item(item)
49
+ results.append(result)
50
+ ```
51
+
52
+ ### Error Handling with TaskGroup
53
+
54
+ ```python
55
+ async def resilient_batch(items: list[Item]) -> tuple[list[Result], list[Error]]:
56
+ results: list[Result] = []
57
+ errors: list[Error] = []
58
+
59
+ # TaskGroup cancels all tasks if one raises — wrap individual tasks
60
+ async def safe_process(item: Item) -> None:
61
+ try:
62
+ result = await process_item(item)
63
+ results.append(result)
64
+ except ProcessingError as e:
65
+ errors.append(Error(item_id=item.id, message=str(e)))
66
+
67
+ async with asyncio.TaskGroup() as tg:
68
+ for item in items:
69
+ tg.create_task(safe_process(item))
70
+
71
+ return results, errors
72
+ ```
73
+
74
+ ## Async Generators
75
+
76
+ ### Streaming Results
77
+
78
+ ```python
79
+ from typing import AsyncGenerator
80
+
81
+ async def stream_results(query: str, params: tuple = ()) -> AsyncGenerator[Record, None]:
82
+ async with get_connection() as conn:
83
+ cursor = await conn.execute(query, params)
84
+ async for row in cursor:
85
+ yield Record.from_row(row)
86
+
87
+ # Usage
88
+ async for record in stream_results("SELECT * FROM events WHERE type = ?", ("click",)):
89
+ await process(record)
90
+ ```
91
+
92
+ ### Async Generator with Cleanup
93
+
94
+ ```python
95
+ async def paginated_fetch(url: str, page_size: int = 100) -> AsyncGenerator[Item, None]:
96
+ page = 0
97
+ while True:
98
+ response = await fetch_page(url, page=page, size=page_size)
99
+ if not response.items:
100
+ break
101
+ for item in response.items:
102
+ yield item
103
+ page += 1
104
+ ```
105
+
106
+ ## Semaphore for Rate Limiting
107
+
108
+ ### Bounded Concurrency
109
+
110
+ ```python
111
+ async def fetch_all(urls: list[str], max_concurrent: int = 10) -> list[Response]:
112
+ semaphore = asyncio.Semaphore(max_concurrent)
113
+
114
+ async def bounded_fetch(url: str) -> Response:
115
+ async with semaphore:
116
+ return await fetch(url)
117
+
118
+ return await asyncio.gather(*[bounded_fetch(url) for url in urls])
119
+ ```
120
+
121
+ ## aiohttp Patterns
122
+
123
+ ### Client Session Management
124
+
125
+ ```python
126
+ from contextlib import asynccontextmanager
127
+ from typing import AsyncGenerator
128
+
129
+ @asynccontextmanager
130
+ async def api_client(base_url: str) -> AsyncGenerator[aiohttp.ClientSession, None]:
131
+ timeout = aiohttp.ClientTimeout(total=30)
132
+ async with aiohttp.ClientSession(base_url, timeout=timeout) as session:
133
+ yield session
134
+
135
+ # Usage — session reused for multiple requests
136
+ async def sync_users(user_ids: list[str]) -> list[User]:
137
+ async with api_client("https://api.example.com") as client:
138
+ tasks = [fetch_user_with_session(client, uid) for uid in user_ids]
139
+ return await asyncio.gather(*tasks)
140
+
141
+ async def fetch_user_with_session(
142
+ client: aiohttp.ClientSession, user_id: str
143
+ ) -> User:
144
+ async with client.get(f"/users/{user_id}") as response:
145
+ response.raise_for_status()
146
+ data = await response.json()
147
+ return User.model_validate(data)
148
+ ```
149
+
150
+ ## Timeout Patterns
151
+
152
+ ### Per-Operation Timeout
153
+
154
+ ```python
155
+ async def fetch_with_timeout(url: str, timeout_seconds: float = 5.0) -> dict[str, Any]:
156
+ try:
157
+ async with asyncio.timeout(timeout_seconds):
158
+ return await fetch(url)
159
+ except TimeoutError:
160
+ raise OperationTimeout(f"Request to {url} timed out after {timeout_seconds}s")
161
+ ```
162
+
163
+ ## Anti-Patterns
164
+
165
+ ### Blocking Calls in Async Code
166
+
167
+ ```python
168
+ # VIOLATION: Blocks the event loop
169
+ async def bad_fetch(url: str) -> str:
170
+ import requests
171
+ return requests.get(url).text # Blocks entire event loop!
172
+
173
+ # CORRECT: Use async library or run in executor
174
+ async def good_fetch(url: str) -> str:
175
+ async with aiohttp.ClientSession() as session:
176
+ async with session.get(url) as response:
177
+ return await response.text()
178
+
179
+ # CORRECT: Offload blocking call to thread pool
180
+ async def run_blocking(func: Callable[..., R], *args: Any) -> R:
181
+ loop = asyncio.get_running_loop()
182
+ return await loop.run_in_executor(None, func, *args)
183
+ ```
184
+
185
+ ### Fire-and-Forget Without Error Handling
186
+
187
+ ```python
188
+ # VIOLATION: Exception silently lost
189
+ async def bad_handler(event: Event) -> None:
190
+ asyncio.create_task(send_notification(event)) # Error vanishes
191
+
192
+ # CORRECT: Track the task and handle errors
193
+ async def good_handler(event: Event) -> None:
194
+ task = asyncio.create_task(send_notification(event))
195
+ task.add_done_callback(handle_task_exception)
196
+
197
+ def handle_task_exception(task: asyncio.Task[Any]) -> None:
198
+ if not task.cancelled() and task.exception() is not None:
199
+ logger.error("Background task failed", exc_info=task.exception())
200
+ ```
201
+
202
+ ### Sequential When Parallel Is Safe
203
+
204
+ ```python
205
+ # VIOLATION: Unnecessarily sequential — 3x slower
206
+ async def slow_load(user_id: str) -> Dashboard:
207
+ user = await fetch_user(user_id)
208
+ orders = await fetch_orders(user_id)
209
+ prefs = await fetch_preferences(user_id)
210
+ return Dashboard(user=user, orders=orders, preferences=prefs)
211
+
212
+ # CORRECT: Concurrent — ~1x latency
213
+ async def fast_load(user_id: str) -> Dashboard:
214
+ user, orders, prefs = await asyncio.gather(
215
+ fetch_user(user_id),
216
+ fetch_orders(user_id),
217
+ fetch_preferences(user_id),
218
+ )
219
+ return Dashboard(user=user, orders=orders, preferences=prefs)
220
+ ```
@@ -0,0 +1,128 @@
1
+ # Python Issue Detection
2
+
3
+ Grep patterns for finding common Python issues. Reference from main SKILL.md.
4
+
5
+ ## Type Safety Detection
6
+
7
+ ### Missing Type Hints
8
+
9
+ ```bash
10
+ # Functions without return type annotation
11
+ grep -rn "def .*):$" --include="*.py" | grep -v "->.*:"
12
+
13
+ # Functions without any annotations
14
+ grep -rn "def .*([a-z_][a-z_0-9]*\s*," --include="*.py" | grep -v ":"
15
+ ```
16
+
17
+ ### Bare Except Clauses
18
+
19
+ ```bash
20
+ # Bare except (catches everything)
21
+ grep -rn "except:" --include="*.py"
22
+
23
+ # Overly broad except Exception
24
+ grep -rn "except Exception:" --include="*.py" | grep -v "# noqa"
25
+ ```
26
+
27
+ ## Data Modeling Detection
28
+
29
+ ### Mutable Default Arguments
30
+
31
+ ```bash
32
+ # List defaults
33
+ grep -rn "def .*=\s*\[\]" --include="*.py"
34
+
35
+ # Dict defaults
36
+ grep -rn "def .*=\s*{}" --include="*.py"
37
+
38
+ # Set defaults
39
+ grep -rn "def .*=\s*set()" --include="*.py"
40
+ ```
41
+
42
+ ### Raw Dict Usage Instead of Dataclasses
43
+
44
+ ```bash
45
+ # Dict literals assigned to typed variables
46
+ grep -rn ': dict\[' --include="*.py" | grep "= {"
47
+
48
+ # Nested dict access patterns (fragile)
49
+ grep -rn '\["[a-z].*\]\["' --include="*.py"
50
+ ```
51
+
52
+ ## Anti-Pattern Detection
53
+
54
+ ### Assert in Production Code
55
+
56
+ ```bash
57
+ # Assert statements outside test files
58
+ grep -rn "^ assert " --include="*.py" | grep -v "test_\|_test\.py\|tests/"
59
+ ```
60
+
61
+ ### Global Mutable State
62
+
63
+ ```bash
64
+ # Global variable assignment
65
+ grep -rn "^[a-z_].*= \[\]\|^[a-z_].*= {}\|^[a-z_].*= set()" --include="*.py"
66
+
67
+ # Global keyword usage
68
+ grep -rn "global " --include="*.py"
69
+ ```
70
+
71
+ ### Old-Style String Formatting
72
+
73
+ ```bash
74
+ # Percent formatting
75
+ grep -rn '"%.*" %' --include="*.py"
76
+ grep -rn "'%.*' %" --include="*.py"
77
+
78
+ # .format() where f-strings would be cleaner
79
+ grep -rn '\.format(' --include="*.py"
80
+ ```
81
+
82
+ ### Wildcard Imports
83
+
84
+ ```bash
85
+ # Star imports
86
+ grep -rn "from .* import \*" --include="*.py"
87
+ ```
88
+
89
+ ## Async Detection
90
+
91
+ ### Missing Await
92
+
93
+ ```bash
94
+ # Coroutine called without await (common mistake)
95
+ grep -rn "async def" --include="*.py" -l | xargs grep -n "[^await ]fetch\|[^await ]save\|[^await ]send"
96
+ ```
97
+
98
+ ### Blocking Calls in Async Code
99
+
100
+ ```bash
101
+ # time.sleep in async context
102
+ grep -rn "time\.sleep" --include="*.py"
103
+
104
+ # requests library in async files (should use aiohttp/httpx)
105
+ grep -rn "import requests\|from requests" --include="*.py"
106
+ ```
107
+
108
+ ## Security Detection
109
+
110
+ ### Unsafe Deserialization
111
+
112
+ ```bash
113
+ # pickle usage (arbitrary code execution risk)
114
+ grep -rn "pickle\.loads\|pickle\.load(" --include="*.py"
115
+
116
+ # yaml.load without SafeLoader
117
+ grep -rn "yaml\.load(" --include="*.py" | grep -v "SafeLoader\|safe_load"
118
+ ```
119
+
120
+ ### Shell Injection
121
+
122
+ ```bash
123
+ # subprocess with shell=True
124
+ grep -rn "shell=True" --include="*.py"
125
+
126
+ # os.system calls
127
+ grep -rn "os\.system(" --include="*.py"
128
+ ```
@@ -0,0 +1,226 @@
1
+ # Python Correct Patterns
2
+
3
+ Extended correct patterns for Python development. Reference from main SKILL.md.
4
+
5
+ ## Dependency Injection
6
+
7
+ ### Constructor Injection
8
+
9
+ ```python
10
+ from typing import Protocol
11
+
12
+ class EmailSender(Protocol):
13
+ def send(self, to: str, subject: str, body: str) -> None: ...
14
+
15
+ class UserService:
16
+ def __init__(self, repo: UserRepository, emailer: EmailSender) -> None:
17
+ self._repo = repo
18
+ self._emailer = emailer
19
+
20
+ def create_user(self, request: CreateUserRequest) -> User:
21
+ user = User(name=request.name, email=request.email)
22
+ saved = self._repo.save(user)
23
+ self._emailer.send(user.email, "Welcome", f"Hello {user.name}")
24
+ return saved
25
+
26
+ # Easy to test — inject fakes
27
+ service = UserService(repo=FakeUserRepo(), emailer=FakeEmailSender())
28
+ ```
29
+
30
+ ### Factory Functions
31
+
32
+ ```python
33
+ def create_app(config: AppConfig) -> Flask:
34
+ app = Flask(__name__)
35
+ db = Database(config.database_url)
36
+ cache = RedisCache(config.redis_url)
37
+ user_service = UserService(repo=SqlUserRepo(db), emailer=SmtpSender(config.smtp))
38
+ register_routes(app, user_service)
39
+ return app
40
+ ```
41
+
42
+ ## Decorator Patterns
43
+
44
+ ### Retry Decorator
45
+
46
+ ```python
47
+ import functools
48
+ import time
49
+ from typing import TypeVar, Callable, ParamSpec
50
+
51
+ P = ParamSpec("P")
52
+ R = TypeVar("R")
53
+
54
+ def retry(
55
+ max_attempts: int = 3,
56
+ delay: float = 1.0,
57
+ exceptions: tuple[type[Exception], ...] = (Exception,),
58
+ ) -> Callable[[Callable[P, R]], Callable[P, R]]:
59
+ def decorator(func: Callable[P, R]) -> Callable[P, R]:
60
+ @functools.wraps(func)
61
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
62
+ last_error: Exception | None = None
63
+ for attempt in range(max_attempts):
64
+ try:
65
+ return func(*args, **kwargs)
66
+ except exceptions as e:
67
+ last_error = e
68
+ if attempt < max_attempts - 1:
69
+ time.sleep(delay * (2 ** attempt))
70
+ raise last_error # type: ignore[misc]
71
+ return wrapper
72
+ return decorator
73
+
74
+ @retry(max_attempts=3, delay=0.5, exceptions=(ConnectionError, TimeoutError))
75
+ def fetch_data(url: str) -> dict[str, Any]:
76
+ ...
77
+ ```
78
+
79
+ ### Validation Decorator
80
+
81
+ ```python
82
+ def validate_input(schema: type[BaseModel]):
83
+ def decorator(func: Callable[P, R]) -> Callable[P, R]:
84
+ @functools.wraps(func)
85
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
86
+ # Validate first positional arg after self/cls
87
+ data = args[1] if len(args) > 1 else args[0]
88
+ schema.model_validate(data)
89
+ return func(*args, **kwargs)
90
+ return wrapper
91
+ return decorator
92
+ ```
93
+
94
+ ## Context Manager Patterns
95
+
96
+ ### Database Transaction
97
+
98
+ ```python
99
+ from contextlib import contextmanager
100
+ from typing import Generator
101
+
102
+ @contextmanager
103
+ def transaction(session: Session) -> Generator[Session, None, None]:
104
+ try:
105
+ yield session
106
+ session.commit()
107
+ except Exception:
108
+ session.rollback()
109
+ raise
110
+ finally:
111
+ session.close()
112
+
113
+ # Usage
114
+ with transaction(db.session()) as session:
115
+ session.add(user)
116
+ session.add(audit_log)
117
+ ```
118
+
119
+ ### Temporary Directory
120
+
121
+ ```python
122
+ from contextlib import contextmanager
123
+ from pathlib import Path
124
+ import tempfile
125
+ import shutil
126
+
127
+ @contextmanager
128
+ def temp_workspace() -> Generator[Path, None, None]:
129
+ path = Path(tempfile.mkdtemp())
130
+ try:
131
+ yield path
132
+ finally:
133
+ shutil.rmtree(path, ignore_errors=True)
134
+ ```
135
+
136
+ ## Pytest Fixture Patterns
137
+
138
+ ### Service Fixtures with DI
139
+
140
+ ```python
141
+ import pytest
142
+
143
+ @pytest.fixture
144
+ def fake_repo() -> FakeUserRepo:
145
+ return FakeUserRepo()
146
+
147
+ @pytest.fixture
148
+ def fake_emailer() -> FakeEmailSender:
149
+ return FakeEmailSender()
150
+
151
+ @pytest.fixture
152
+ def user_service(fake_repo: FakeUserRepo, fake_emailer: FakeEmailSender) -> UserService:
153
+ return UserService(repo=fake_repo, emailer=fake_emailer)
154
+
155
+ def test_create_user_sends_welcome_email(
156
+ user_service: UserService,
157
+ fake_emailer: FakeEmailSender,
158
+ ) -> None:
159
+ user_service.create_user(CreateUserRequest(name="Alice", email="a@b.com"))
160
+ assert fake_emailer.sent_count == 1
161
+ assert fake_emailer.last_to == "a@b.com"
162
+ ```
163
+
164
+ ### Parametrized Tests
165
+
166
+ ```python
167
+ @pytest.mark.parametrize(
168
+ "input_age, expected_valid",
169
+ [
170
+ (25, True),
171
+ (0, True),
172
+ (150, True),
173
+ (-1, False),
174
+ (151, False),
175
+ (None, False),
176
+ ],
177
+ )
178
+ def test_age_validation(input_age: int | None, expected_valid: bool) -> None:
179
+ result = validate_age(input_age)
180
+ assert result.is_valid == expected_valid
181
+ ```
182
+
183
+ ## Structured Logging
184
+
185
+ ```python
186
+ import structlog
187
+
188
+ logger = structlog.get_logger()
189
+
190
+ def process_order(order: Order) -> OrderResult:
191
+ log = logger.bind(order_id=order.id, user_id=order.user_id)
192
+ log.info("processing_order", item_count=len(order.items))
193
+
194
+ try:
195
+ result = fulfill(order)
196
+ log.info("order_fulfilled", total=result.total)
197
+ return result
198
+ except InsufficientStockError as e:
199
+ log.warning("order_failed_stock", item_id=e.item_id)
200
+ raise
201
+ ```
202
+
203
+ ## Enum Patterns
204
+
205
+ ```python
206
+ from enum import Enum, auto
207
+
208
+ class OrderStatus(Enum):
209
+ PENDING = auto()
210
+ PROCESSING = auto()
211
+ SHIPPED = auto()
212
+ DELIVERED = auto()
213
+ CANCELLED = auto()
214
+
215
+ @property
216
+ def is_terminal(self) -> bool:
217
+ return self in (OrderStatus.DELIVERED, OrderStatus.CANCELLED)
218
+
219
+ def can_transition_to(self, target: "OrderStatus") -> bool:
220
+ valid = {
221
+ OrderStatus.PENDING: {OrderStatus.PROCESSING, OrderStatus.CANCELLED},
222
+ OrderStatus.PROCESSING: {OrderStatus.SHIPPED, OrderStatus.CANCELLED},
223
+ OrderStatus.SHIPPED: {OrderStatus.DELIVERED},
224
+ }
225
+ return target in valid.get(self, set())
226
+ ```