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.
- package/CHANGELOG.md +69 -0
- package/README.md +35 -11
- package/dist/cli.js +5 -1
- package/dist/commands/ambient.d.ts +18 -0
- package/dist/commands/ambient.js +136 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +97 -10
- package/dist/commands/memory.d.ts +22 -0
- package/dist/commands/memory.js +175 -0
- package/dist/commands/uninstall.js +72 -5
- package/dist/plugins.js +74 -3
- package/dist/utils/post-install.d.ts +12 -0
- package/dist/utils/post-install.js +82 -1
- package/dist/utils/safe-delete-install.d.ts +7 -0
- package/dist/utils/safe-delete-install.js +40 -5
- package/package.json +2 -1
- package/plugins/devflow-accessibility/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-ambient/.claude-plugin/plugin.json +7 -0
- package/plugins/devflow-ambient/README.md +49 -0
- package/plugins/devflow-ambient/commands/ambient.md +110 -0
- package/plugins/devflow-ambient/skills/ambient-router/SKILL.md +89 -0
- package/plugins/devflow-ambient/skills/ambient-router/references/skill-catalog.md +68 -0
- package/plugins/devflow-audit-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-code-review/.claude-plugin/plugin.json +1 -4
- package/plugins/devflow-code-review/agents/reviewer.md +8 -0
- package/plugins/devflow-code-review/commands/code-review-teams.md +11 -1
- package/plugins/devflow-code-review/commands/code-review.md +12 -2
- package/plugins/devflow-core-skills/.claude-plugin/plugin.json +3 -6
- package/plugins/devflow-core-skills/skills/docs-framework/SKILL.md +10 -6
- package/plugins/devflow-core-skills/skills/test-driven-development/SKILL.md +139 -0
- package/plugins/devflow-core-skills/skills/test-driven-development/references/rationalization-prevention.md +111 -0
- package/plugins/devflow-debug/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-frontend-design/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-go/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-go/skills/go/SKILL.md +187 -0
- package/plugins/devflow-go/skills/go/references/concurrency.md +312 -0
- package/plugins/devflow-go/skills/go/references/detection.md +129 -0
- package/plugins/devflow-go/skills/go/references/patterns.md +232 -0
- package/plugins/devflow-go/skills/go/references/violations.md +205 -0
- package/plugins/devflow-implement/.claude-plugin/plugin.json +1 -3
- package/plugins/devflow-implement/agents/coder.md +11 -6
- package/plugins/devflow-java/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-java/skills/java/SKILL.md +183 -0
- package/plugins/devflow-java/skills/java/references/detection.md +120 -0
- package/plugins/devflow-java/skills/java/references/modern-java.md +270 -0
- package/plugins/devflow-java/skills/java/references/patterns.md +235 -0
- package/plugins/devflow-java/skills/java/references/violations.md +213 -0
- package/plugins/devflow-python/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-python/skills/python/SKILL.md +188 -0
- package/plugins/devflow-python/skills/python/references/async.md +220 -0
- package/plugins/devflow-python/skills/python/references/detection.md +128 -0
- package/plugins/devflow-python/skills/python/references/patterns.md +226 -0
- package/plugins/devflow-python/skills/python/references/violations.md +204 -0
- package/plugins/devflow-react/.claude-plugin/plugin.json +15 -0
- package/plugins/{devflow-core-skills → devflow-react}/skills/react/SKILL.md +1 -1
- package/plugins/{devflow-core-skills → devflow-react}/skills/react/references/patterns.md +3 -3
- package/plugins/devflow-resolve/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-rust/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-rust/skills/rust/SKILL.md +193 -0
- package/plugins/devflow-rust/skills/rust/references/detection.md +131 -0
- package/plugins/devflow-rust/skills/rust/references/ownership.md +242 -0
- package/plugins/devflow-rust/skills/rust/references/patterns.md +210 -0
- package/plugins/devflow-rust/skills/rust/references/violations.md +191 -0
- package/plugins/devflow-self-review/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-specify/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-typescript/.claude-plugin/plugin.json +15 -0
- package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/references/patterns.md +3 -3
- package/scripts/hooks/ambient-prompt.sh +48 -0
- package/scripts/hooks/background-memory-update.sh +49 -8
- package/scripts/hooks/ensure-memory-gitignore.sh +17 -0
- package/scripts/hooks/pre-compact-memory.sh +12 -6
- package/scripts/hooks/session-start-memory.sh +50 -8
- package/scripts/hooks/stop-update-memory.sh +10 -6
- package/shared/agents/coder.md +11 -6
- package/shared/agents/reviewer.md +8 -0
- package/shared/skills/ambient-router/SKILL.md +89 -0
- package/shared/skills/ambient-router/references/skill-catalog.md +68 -0
- package/shared/skills/docs-framework/SKILL.md +10 -6
- package/shared/skills/go/SKILL.md +187 -0
- package/shared/skills/go/references/concurrency.md +312 -0
- package/shared/skills/go/references/detection.md +129 -0
- package/shared/skills/go/references/patterns.md +232 -0
- package/shared/skills/go/references/violations.md +205 -0
- package/shared/skills/java/SKILL.md +183 -0
- package/shared/skills/java/references/detection.md +120 -0
- package/shared/skills/java/references/modern-java.md +270 -0
- package/shared/skills/java/references/patterns.md +235 -0
- package/shared/skills/java/references/violations.md +213 -0
- package/shared/skills/python/SKILL.md +188 -0
- package/shared/skills/python/references/async.md +220 -0
- package/shared/skills/python/references/detection.md +128 -0
- package/shared/skills/python/references/patterns.md +226 -0
- package/shared/skills/python/references/violations.md +204 -0
- package/shared/skills/react/SKILL.md +1 -1
- package/shared/skills/react/references/patterns.md +3 -3
- package/shared/skills/rust/SKILL.md +193 -0
- package/shared/skills/rust/references/detection.md +131 -0
- package/shared/skills/rust/references/ownership.md +242 -0
- package/shared/skills/rust/references/patterns.md +210 -0
- package/shared/skills/rust/references/violations.md +191 -0
- package/shared/skills/test-driven-development/SKILL.md +139 -0
- package/shared/skills/test-driven-development/references/rationalization-prevention.md +111 -0
- package/shared/skills/typescript/references/patterns.md +3 -3
- package/src/templates/managed-settings.json +14 -0
- package/plugins/devflow-code-review/skills/react/SKILL.md +0 -276
- package/plugins/devflow-code-review/skills/react/references/patterns.md +0 -1331
- package/plugins/devflow-core-skills/skills/accessibility/SKILL.md +0 -229
- package/plugins/devflow-core-skills/skills/accessibility/references/detection.md +0 -171
- package/plugins/devflow-core-skills/skills/accessibility/references/patterns.md +0 -670
- package/plugins/devflow-core-skills/skills/accessibility/references/violations.md +0 -419
- package/plugins/devflow-core-skills/skills/frontend-design/SKILL.md +0 -254
- package/plugins/devflow-core-skills/skills/frontend-design/references/detection.md +0 -184
- package/plugins/devflow-core-skills/skills/frontend-design/references/patterns.md +0 -511
- package/plugins/devflow-core-skills/skills/frontend-design/references/violations.md +0 -453
- package/plugins/devflow-core-skills/skills/react/references/violations.md +0 -565
- package/plugins/devflow-implement/skills/accessibility/SKILL.md +0 -229
- package/plugins/devflow-implement/skills/accessibility/references/detection.md +0 -171
- package/plugins/devflow-implement/skills/accessibility/references/patterns.md +0 -670
- package/plugins/devflow-implement/skills/accessibility/references/violations.md +0 -419
- package/plugins/devflow-implement/skills/frontend-design/SKILL.md +0 -254
- package/plugins/devflow-implement/skills/frontend-design/references/detection.md +0 -184
- package/plugins/devflow-implement/skills/frontend-design/references/patterns.md +0 -511
- package/plugins/devflow-implement/skills/frontend-design/references/violations.md +0 -453
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/SKILL.md +0 -0
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/detection.md +0 -0
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/patterns.md +0 -0
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/violations.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/SKILL.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/detection.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/patterns.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/violations.md +0 -0
- /package/plugins/{devflow-code-review → devflow-react}/skills/react/references/violations.md +0 -0
- /package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/SKILL.md +0 -0
- /package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/references/violations.md +0 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Common Java Violations
|
|
2
|
+
|
|
3
|
+
Extended violation patterns for Java reviews. Reference from main SKILL.md.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Null Returns
|
|
8
|
+
|
|
9
|
+
### Returning null Instead of Optional
|
|
10
|
+
|
|
11
|
+
```java
|
|
12
|
+
// VIOLATION: Null return forces callers to null-check
|
|
13
|
+
public User findById(String id) {
|
|
14
|
+
return userMap.get(id); // Returns null if absent
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Callers must remember to check:
|
|
18
|
+
User user = findById(id);
|
|
19
|
+
user.getName(); // NullPointerException if not found
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Nullable Collections
|
|
23
|
+
|
|
24
|
+
```java
|
|
25
|
+
// VIOLATION: Returning null instead of empty collection
|
|
26
|
+
public List<Order> getOrders(String userId) {
|
|
27
|
+
List<Order> orders = orderMap.get(userId);
|
|
28
|
+
return orders; // null if user has no orders
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Callers iterate without checking:
|
|
32
|
+
for (Order o : getOrders(userId)) { // NullPointerException
|
|
33
|
+
process(o);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Raw Types
|
|
40
|
+
|
|
41
|
+
### Unparameterized Generics
|
|
42
|
+
|
|
43
|
+
```java
|
|
44
|
+
// VIOLATION: Raw List - no type safety
|
|
45
|
+
List users = new ArrayList();
|
|
46
|
+
users.add("not a user"); // No compile error
|
|
47
|
+
users.add(42); // No compile error
|
|
48
|
+
User first = (User) users.get(0); // ClassCastException at runtime
|
|
49
|
+
|
|
50
|
+
// VIOLATION: Raw Map
|
|
51
|
+
Map cache = new HashMap();
|
|
52
|
+
cache.put(123, "value");
|
|
53
|
+
String val = (String) cache.get(123); // Unsafe cast
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Raw Type in Method Signatures
|
|
57
|
+
|
|
58
|
+
```java
|
|
59
|
+
// VIOLATION: Raw Comparable
|
|
60
|
+
public class Price implements Comparable {
|
|
61
|
+
public int compareTo(Object other) {
|
|
62
|
+
return Double.compare(this.amount, ((Price) other).amount); // Unsafe cast
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// VIOLATION: Raw Iterator
|
|
67
|
+
Iterator it = collection.iterator();
|
|
68
|
+
while (it.hasNext()) {
|
|
69
|
+
String s = (String) it.next(); // Unsafe cast
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Checked Exception Abuse
|
|
76
|
+
|
|
77
|
+
### Broad Throws Declarations
|
|
78
|
+
|
|
79
|
+
```java
|
|
80
|
+
// VIOLATION: throws Exception - hides what can actually go wrong
|
|
81
|
+
public User createUser(String name) throws Exception {
|
|
82
|
+
// Callers must catch Exception, can't handle specific failures
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// VIOLATION: Wrapping everything in checked exceptions
|
|
86
|
+
public void process(String data) throws ProcessingException {
|
|
87
|
+
try {
|
|
88
|
+
Integer.parseInt(data);
|
|
89
|
+
} catch (NumberFormatException e) {
|
|
90
|
+
throw new ProcessingException("Failed", e); // Unnecessary wrapping
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Swallowed Exceptions
|
|
96
|
+
|
|
97
|
+
```java
|
|
98
|
+
// VIOLATION: Empty catch block
|
|
99
|
+
try {
|
|
100
|
+
connection.close();
|
|
101
|
+
} catch (SQLException e) {
|
|
102
|
+
// silently ignored - resource leak hidden
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// VIOLATION: Catching and logging only
|
|
106
|
+
try {
|
|
107
|
+
processPayment(order);
|
|
108
|
+
} catch (PaymentException e) {
|
|
109
|
+
logger.error("Payment failed", e);
|
|
110
|
+
// Continues as if nothing happened - order in inconsistent state
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Mutable Data Objects
|
|
117
|
+
|
|
118
|
+
### JavaBean-Style Mutability
|
|
119
|
+
|
|
120
|
+
```java
|
|
121
|
+
// VIOLATION: Mutable DTO with setters
|
|
122
|
+
public class UserDTO {
|
|
123
|
+
private String name;
|
|
124
|
+
private String email;
|
|
125
|
+
|
|
126
|
+
public void setName(String name) { this.name = name; }
|
|
127
|
+
public void setEmail(String email) { this.email = email; }
|
|
128
|
+
public String getName() { return name; }
|
|
129
|
+
public String getEmail() { return email; }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Anyone can modify at any time:
|
|
133
|
+
dto.setName("changed"); // No control over when/where mutation happens
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Exposing Internal Mutable State
|
|
137
|
+
|
|
138
|
+
```java
|
|
139
|
+
// VIOLATION: Getter returns mutable internal list
|
|
140
|
+
public class Team {
|
|
141
|
+
private List<String> members = new ArrayList<>();
|
|
142
|
+
|
|
143
|
+
public List<String> getMembers() {
|
|
144
|
+
return members; // Caller can modify internal state
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// External code breaks encapsulation:
|
|
149
|
+
team.getMembers().clear(); // Empties the team's internal list
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Deep Inheritance
|
|
155
|
+
|
|
156
|
+
### Fragile Base Class
|
|
157
|
+
|
|
158
|
+
```java
|
|
159
|
+
// VIOLATION: Deep hierarchy creates tight coupling
|
|
160
|
+
public abstract class AbstractEntity { ... }
|
|
161
|
+
public abstract class AbstractAuditableEntity extends AbstractEntity { ... }
|
|
162
|
+
public abstract class AbstractVersionedEntity extends AbstractAuditableEntity { ... }
|
|
163
|
+
public class User extends AbstractVersionedEntity { ... }
|
|
164
|
+
|
|
165
|
+
// Changing any base class ripples through all descendants
|
|
166
|
+
// Testing requires understanding 4 levels of behavior
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Inheritance for Code Reuse
|
|
170
|
+
|
|
171
|
+
```java
|
|
172
|
+
// VIOLATION: Extending just to reuse utility methods
|
|
173
|
+
public class OrderService extends BaseService {
|
|
174
|
+
// Only extends BaseService to get logAndAudit() method
|
|
175
|
+
public void processOrder(Order order) {
|
|
176
|
+
logAndAudit("processing", order.getId()); // Inherited utility
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Should be: inject a LogAuditService instead
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Concurrency Violations
|
|
185
|
+
|
|
186
|
+
### Shared Mutable State Without Synchronization
|
|
187
|
+
|
|
188
|
+
```java
|
|
189
|
+
// VIOLATION: HashMap accessed from multiple threads
|
|
190
|
+
private Map<String, Session> sessions = new HashMap<>();
|
|
191
|
+
|
|
192
|
+
public void addSession(String id, Session s) {
|
|
193
|
+
sessions.put(id, s); // Not thread-safe
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Double-Checked Locking Done Wrong
|
|
198
|
+
|
|
199
|
+
```java
|
|
200
|
+
// VIOLATION: Missing volatile on lazily-initialized field
|
|
201
|
+
private ExpensiveObject instance;
|
|
202
|
+
|
|
203
|
+
public ExpensiveObject getInstance() {
|
|
204
|
+
if (instance == null) {
|
|
205
|
+
synchronized (this) {
|
|
206
|
+
if (instance == null) {
|
|
207
|
+
instance = new ExpensiveObject(); // Partially constructed object visible
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return instance;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: python
|
|
3
|
+
description: This skill should be used when the user works with Python files (.py), asks about "type hints", "protocols", "dataclasses", "async/await", "decorators", or discusses Pythonic patterns and data modeling. Provides patterns for type safety, error handling, data modeling, and async programming.
|
|
4
|
+
user-invocable: false
|
|
5
|
+
allowed-tools: Read, Grep, Glob
|
|
6
|
+
activation:
|
|
7
|
+
file-patterns:
|
|
8
|
+
- "**/*.py"
|
|
9
|
+
exclude:
|
|
10
|
+
- "venv/**"
|
|
11
|
+
- ".venv/**"
|
|
12
|
+
- "**/__pycache__/**"
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Python Patterns
|
|
16
|
+
|
|
17
|
+
Reference for Python-specific patterns, type safety, and idioms.
|
|
18
|
+
|
|
19
|
+
## Iron Law
|
|
20
|
+
|
|
21
|
+
> **EXPLICIT IS BETTER THAN IMPLICIT**
|
|
22
|
+
>
|
|
23
|
+
> Type-hint every function signature. Name every exception. Use dataclasses over raw dicts.
|
|
24
|
+
> Python's flexibility is a strength only when boundaries are explicit. Implicit behavior
|
|
25
|
+
> causes debugging nightmares and makes codebases hostile to newcomers.
|
|
26
|
+
|
|
27
|
+
## When This Skill Activates
|
|
28
|
+
|
|
29
|
+
- Working with Python codebases
|
|
30
|
+
- Designing typed APIs with type hints
|
|
31
|
+
- Modeling data with dataclasses or Pydantic
|
|
32
|
+
- Implementing async code
|
|
33
|
+
- Structuring Python packages
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Type Safety
|
|
38
|
+
|
|
39
|
+
### Type Hint Everything
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
# BAD: def process(data, config): ...
|
|
43
|
+
# GOOD:
|
|
44
|
+
def process(data: list[dict[str, Any]], config: AppConfig) -> ProcessResult:
|
|
45
|
+
...
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Use Protocols for Structural Typing
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from typing import Protocol
|
|
52
|
+
|
|
53
|
+
class Repository(Protocol):
|
|
54
|
+
def find_by_id(self, id: str) -> User | None: ...
|
|
55
|
+
def save(self, entity: User) -> User: ...
|
|
56
|
+
|
|
57
|
+
# Any class with these methods satisfies Repository — no inheritance needed
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Strict Optional Handling
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
# BAD: def get_name(user): return user.name
|
|
64
|
+
# GOOD:
|
|
65
|
+
def get_name(user: User | None) -> str:
|
|
66
|
+
if user is None:
|
|
67
|
+
return "Anonymous"
|
|
68
|
+
return user.name
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Error Handling
|
|
74
|
+
|
|
75
|
+
### Custom Exception Hierarchies
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
class AppError(Exception):
|
|
79
|
+
"""Base application error."""
|
|
80
|
+
|
|
81
|
+
class NotFoundError(AppError):
|
|
82
|
+
def __init__(self, entity: str, id: str) -> None:
|
|
83
|
+
super().__init__(f"{entity} {id} not found")
|
|
84
|
+
self.entity = entity
|
|
85
|
+
self.id = id
|
|
86
|
+
|
|
87
|
+
class ValidationError(AppError):
|
|
88
|
+
def __init__(self, field: str, message: str) -> None:
|
|
89
|
+
super().__init__(f"Validation failed for {field}: {message}")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Context Managers for Resources
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from contextlib import contextmanager
|
|
96
|
+
|
|
97
|
+
@contextmanager
|
|
98
|
+
def database_transaction(conn: Connection):
|
|
99
|
+
try:
|
|
100
|
+
yield conn
|
|
101
|
+
conn.commit()
|
|
102
|
+
except Exception:
|
|
103
|
+
conn.rollback()
|
|
104
|
+
raise
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Data Modeling
|
|
110
|
+
|
|
111
|
+
### Dataclasses Over Raw Dicts
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
# BAD: user = {"name": "Alice", "email": "alice@example.com"}
|
|
115
|
+
# GOOD:
|
|
116
|
+
@dataclass(frozen=True)
|
|
117
|
+
class User:
|
|
118
|
+
name: str
|
|
119
|
+
email: str
|
|
120
|
+
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Pydantic for Validation at Boundaries
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from pydantic import BaseModel, EmailStr
|
|
127
|
+
|
|
128
|
+
class CreateUserRequest(BaseModel):
|
|
129
|
+
name: str
|
|
130
|
+
email: EmailStr
|
|
131
|
+
age: int = Field(ge=0, le=150)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Pythonic Patterns
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
# Comprehensions over loops for transforms
|
|
140
|
+
names = [user.name for user in users if user.active]
|
|
141
|
+
|
|
142
|
+
# Enumerate over manual index tracking
|
|
143
|
+
for i, item in enumerate(items):
|
|
144
|
+
process(i, item)
|
|
145
|
+
|
|
146
|
+
# EAFP: Easier to Ask Forgiveness than Permission
|
|
147
|
+
try:
|
|
148
|
+
value = mapping[key]
|
|
149
|
+
except KeyError:
|
|
150
|
+
value = default
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Anti-Patterns
|
|
156
|
+
|
|
157
|
+
| Pattern | Bad | Good |
|
|
158
|
+
|---------|-----|------|
|
|
159
|
+
| Bare except | `except:` | `except (ValueError, KeyError):` |
|
|
160
|
+
| Mutable default | `def fn(items=[])` | `def fn(items: list | None = None)` |
|
|
161
|
+
| No type hints | `def process(data)` | `def process(data: DataFrame) -> Result` |
|
|
162
|
+
| String typing | `x: "MyClass"` (without reason) | `from __future__ import annotations` |
|
|
163
|
+
| God class | `class App` with 50 methods | Compose smaller focused classes |
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Extended References
|
|
168
|
+
|
|
169
|
+
For additional patterns and examples:
|
|
170
|
+
- `references/violations.md` - Common Python violations
|
|
171
|
+
- `references/patterns.md` - Extended Python patterns
|
|
172
|
+
- `references/detection.md` - Detection patterns for Python issues
|
|
173
|
+
- `references/async.md` - Async Python patterns
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Checklist
|
|
178
|
+
|
|
179
|
+
- [ ] All functions have type hints (params + return)
|
|
180
|
+
- [ ] Custom exceptions with meaningful messages
|
|
181
|
+
- [ ] Dataclasses or Pydantic for structured data
|
|
182
|
+
- [ ] No bare `except:` clauses
|
|
183
|
+
- [ ] No mutable default arguments
|
|
184
|
+
- [ ] Context managers for resource management
|
|
185
|
+
- [ ] `from __future__ import annotations` for forward refs
|
|
186
|
+
- [ ] Protocols for structural typing (not ABC unless needed)
|
|
187
|
+
- [ ] Comprehensions for simple transforms
|
|
188
|
+
- [ ] Tests use pytest with fixtures
|
|
@@ -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
|
+
```
|