cc-codeconductor 0.2.1

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 (93) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +319 -0
  3. package/dist/index.js +12741 -0
  4. package/package.json +51 -0
  5. package/policy.yml +124 -0
  6. package/presets/claude/CLAUDE.md +598 -0
  7. package/presets/claude/commands/cc/feature.md +115 -0
  8. package/presets/claude/commands/cc/fix.md +121 -0
  9. package/presets/claude/commands/cc/refactor.md +148 -0
  10. package/presets/claude/commands/cc/review.md +126 -0
  11. package/presets/claude/commands/cc/tdd-cycle.md +226 -0
  12. package/presets/claude/commands/cc/test-plan.md +138 -0
  13. package/presets/claude/settings.json +37 -0
  14. package/presets/claude/skills/api-versioning/SKILL.md +389 -0
  15. package/presets/claude/skills/django-orm/SKILL.md +455 -0
  16. package/presets/claude/skills/django-testing/SKILL.md +409 -0
  17. package/presets/claude/skills/jpa-postgres/SKILL.md +618 -0
  18. package/presets/claude/skills/python/SKILL.md +606 -0
  19. package/presets/claude/skills/python-django-stack/SKILL.md +492 -0
  20. package/presets/claude/skills/python-fastapi-stack/SKILL.md +457 -0
  21. package/presets/claude/skills/spring-boot-feature/SKILL.md +558 -0
  22. package/presets/claude/skills/spring-boot-kotlin/SKILL.md +400 -0
  23. package/presets/claude/skills/sqlalchemy/SKILL.md +466 -0
  24. package/presets/claude/skills/testing-strategy/SKILL.md +479 -0
  25. package/presets/codex/AGENTS.md +883 -0
  26. package/presets/codex/README.md +102 -0
  27. package/presets/codex/skills/api-versioning/SKILL.md +389 -0
  28. package/presets/codex/skills/django-orm/SKILL.md +455 -0
  29. package/presets/codex/skills/django-testing/SKILL.md +409 -0
  30. package/presets/codex/skills/jpa-postgres/SKILL.md +618 -0
  31. package/presets/codex/skills/python/SKILL.md +606 -0
  32. package/presets/codex/skills/python-django-stack/SKILL.md +492 -0
  33. package/presets/codex/skills/python-fastapi-stack/SKILL.md +457 -0
  34. package/presets/codex/skills/spring-boot-feature/SKILL.md +558 -0
  35. package/presets/codex/skills/spring-boot-kotlin/SKILL.md +400 -0
  36. package/presets/codex/skills/sqlalchemy/SKILL.md +466 -0
  37. package/presets/codex/skills/testing-strategy/SKILL.md +479 -0
  38. package/presets/opencode/README.md +191 -0
  39. package/presets/opencode/agents/architect.md +133 -0
  40. package/presets/opencode/agents/docs.md +113 -0
  41. package/presets/opencode/agents/implementer.md +116 -0
  42. package/presets/opencode/agents/orchestrator.md +248 -0
  43. package/presets/opencode/agents/repo-explorer.md +119 -0
  44. package/presets/opencode/agents/reviewer.md +130 -0
  45. package/presets/opencode/agents/task-coach.md +103 -0
  46. package/presets/opencode/agents/tester.md +269 -0
  47. package/presets/opencode/commands/cc-feature.md +114 -0
  48. package/presets/opencode/commands/cc-fix.md +123 -0
  49. package/presets/opencode/commands/cc-refactor.md +148 -0
  50. package/presets/opencode/commands/cc-review.md +141 -0
  51. package/presets/opencode/commands/cc-tdd-cycle.md +225 -0
  52. package/presets/opencode/commands/cc-test-plan.md +144 -0
  53. package/presets/opencode/opencode.jsonc +89 -0
  54. package/presets/opencode/prompts/v0.1.0/architect.md +213 -0
  55. package/presets/opencode/prompts/v0.1.0/docs.md +181 -0
  56. package/presets/opencode/prompts/v0.1.0/implementer.md +154 -0
  57. package/presets/opencode/prompts/v0.1.0/orchestrator.md +169 -0
  58. package/presets/opencode/prompts/v0.1.0/repo-explorer.md +102 -0
  59. package/presets/opencode/prompts/v0.1.0/reviewer.md +183 -0
  60. package/presets/opencode/prompts/v0.1.0/task-coach.md +142 -0
  61. package/presets/opencode/prompts/v0.1.0/tester.md +160 -0
  62. package/presets/opencode/prompts/v0.2.0/architect.md +219 -0
  63. package/presets/opencode/prompts/v0.2.0/docs.md +187 -0
  64. package/presets/opencode/prompts/v0.2.0/implementer.md +160 -0
  65. package/presets/opencode/prompts/v0.2.0/orchestrator.md +238 -0
  66. package/presets/opencode/prompts/v0.2.0/repo-explorer.md +108 -0
  67. package/presets/opencode/prompts/v0.2.0/reviewer.md +190 -0
  68. package/presets/opencode/prompts/v0.2.0/task-coach.md +153 -0
  69. package/presets/opencode/prompts/v0.2.0/tester.md +249 -0
  70. package/presets/opencode/skills/api-versioning/SKILL.md +388 -0
  71. package/presets/opencode/skills/astro/SKILL.md +313 -0
  72. package/presets/opencode/skills/code-review/SKILL.md +202 -0
  73. package/presets/opencode/skills/django-orm/SKILL.md +455 -0
  74. package/presets/opencode/skills/django-testing/SKILL.md +408 -0
  75. package/presets/opencode/skills/django-uv/SKILL.md +400 -0
  76. package/presets/opencode/skills/jpa-postgres/SKILL.md +617 -0
  77. package/presets/opencode/skills/nextjs-typescript/SKILL.md +385 -0
  78. package/presets/opencode/skills/python/SKILL.md +605 -0
  79. package/presets/opencode/skills/python-django-stack/SKILL.md +491 -0
  80. package/presets/opencode/skills/python-fastapi-stack/SKILL.md +456 -0
  81. package/presets/opencode/skills/security/SKILL.md +376 -0
  82. package/presets/opencode/skills/spring-boot-feature/SKILL.md +557 -0
  83. package/presets/opencode/skills/spring-boot-kotlin/SKILL.md +399 -0
  84. package/presets/opencode/skills/spring-boot-testing-strategy/SKILL.md +470 -0
  85. package/presets/opencode/skills/sqlalchemy/SKILL.md +465 -0
  86. package/presets/opencode/skills/testing-tdd/SKILL.md +586 -0
  87. package/src/presets/council/council.yml +59 -0
  88. package/src/presets/manifests/claude.yml +23 -0
  89. package/src/presets/manifests/codex.yml +12 -0
  90. package/src/presets/manifests/opencode.yml +18 -0
  91. package/src/presets/models/claude.yml +37 -0
  92. package/src/presets/models/codex.yml +38 -0
  93. package/src/presets/models/opencode.yml +37 -0
@@ -0,0 +1,605 @@
1
+ ---
2
+ id: python
3
+ version: 1.0.0
4
+ name: Python
5
+ description: >
6
+ Python development best practices: clean code, patterns,
7
+ type hints, decorators, context managers, and architecture.
8
+ Trigger: When writing Python code anywhere in the project.
9
+
10
+ compatibility:
11
+ tools: [claude, codex, opencode]
12
+ stacks:
13
+ languages: [python]
14
+ frameworks: [django, fastapi, flask]
15
+
16
+ risk:
17
+ level: low
18
+ can_execute_shell: false
19
+ can_modify_files: true
20
+ requires_network: false
21
+
22
+ inputs:
23
+ - Python source files
24
+ - existing functions, classes, or modules
25
+ - business logic implementations to review or refactor
26
+
27
+ outputs:
28
+ - clean Python functions and classes with meaningful names
29
+ - type-annotated public API signatures
30
+ - custom domain exceptions
31
+ - value objects and data classes
32
+ - service and repository pattern implementations
33
+
34
+ quality:
35
+ reviewed_by: codeconductor-core
36
+ version: 0.1.0
37
+ ---
38
+ ## When to Use
39
+
40
+ - Writing any Python code in the project
41
+ - Reviewing code for quality and maintainability
42
+ - Designing new functions, classes, or modules
43
+ - Implementing business logic or utilities
44
+
45
+ ## Clean Code Principles
46
+
47
+ ### Readability > Brevity
48
+
49
+ Code is read more times than it's written. Prioritize clarity over cleverness:
50
+
51
+ ```python
52
+ # WRONG — clever but obscure
53
+ def f(x): return x if x else 0
54
+
55
+ # CORRECT — readable
56
+ def calculate_discount(price, has_discount):
57
+ if not has_discount:
58
+ return 0
59
+ return price * DISCOUNT_RATE
60
+ ```
61
+
62
+ ### Meaningful Names
63
+
64
+ | What | Convention | Example |
65
+ | --------- | ---------------------- | -------------------------------------------- |
66
+ | Variables | descriptive snake_case | `total_price`, `products_list` |
67
+ | Functions | verb snake_case | `get_active_products()`, `calculate_total()` |
68
+ | Classes | PascalCase | `OrderService`, `CartController` |
69
+ | Constants | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT`, `DEFAULT_PAGE_SIZE` |
70
+ | Modules | snake_case | `order_service.py`, `cart_utils.py` |
71
+
72
+ ```python
73
+ # WRONG — cryptic names
74
+ d = 1500
75
+ p = products.filter(a=True)
76
+
77
+ # CORRECT — names that say what they are
78
+ discount_amount = 1500
79
+ active_products = Product.objects.filter(is_active=True)
80
+ ```
81
+
82
+ ### Small Functions
83
+
84
+ A function should do ONE thing. Ideally under 30 lines:
85
+
86
+ ```python
87
+ # WRONG — function does many things
88
+ def process_order(order_data):
89
+ # validates
90
+ # creates order
91
+ # creates items
92
+ # updates stock
93
+ # sends email
94
+ # updates analytics
95
+ ...
96
+
97
+ # CORRECT — small, focused functions
98
+ def process_order(order_data):
99
+ validated = validate_order_data(order_data)
100
+ order = create_order(validated)
101
+ create_order_items(order, validated["items"])
102
+ update_product_stock(validated["items"])
103
+ send_order_confirmation(order)
104
+ track_order_analytics(order)
105
+ return order
106
+ ```
107
+
108
+ ### DRY — Don't Repeat Yourself
109
+
110
+ Extract repeated code into functions or classes:
111
+
112
+ ```python
113
+ # WRONG — repeat logic
114
+ if order.status == "pending":
115
+ send_email(order.customer, "Your order is pending")
116
+ log_event("order_pending", order.id)
117
+
118
+ if order.status == "paid":
119
+ send_email(order.customer, "Your order was paid")
120
+ log_event("order_paid", order.id)
121
+
122
+ # CORRECT — extract to function
123
+ def notify_order_status(order):
124
+ status_messages = {
125
+ "pending": "Your order is pending",
126
+ "paid": "Your order was paid",
127
+ }
128
+ send_email(order.customer, status_messages[order.status])
129
+ log_event(f"order_{order.status}", order.id)
130
+ ```
131
+
132
+ ## Type Hints
133
+
134
+ ### When to Use Them
135
+
136
+ Use type hints on:
137
+
138
+ - Public function signatures (APIs, services)
139
+ - Functions with complex parameters
140
+ - Non-obvious returns
141
+
142
+ ```python
143
+ from typing import Optional, List, Dict, Any
144
+
145
+ # Public functions — use hints
146
+ def calculate_total(items: List[Dict[str, Any]], tax_rate: float) -> float:
147
+ ...
148
+
149
+ def find_product(product_id: int) -> Optional[Product]:
150
+ ...
151
+
152
+ # Private helpers — optional
153
+ def _build_cart_response(cart):
154
+ ...
155
+ ```
156
+
157
+ ### Basic Types
158
+
159
+ ```python
160
+ # Primitives
161
+ name: str = "Product"
162
+ quantity: int = 5
163
+ price: float = 1500.99
164
+ is_active: bool = True
165
+
166
+ # Collections
167
+ products: List[Product] = []
168
+ product_ids: list[int] = []
169
+ metadata: Dict[str, Any] = {}
170
+ items: tuple[str, int] = ("sku", 5)
171
+
172
+ # Optionals
173
+ user: Optional[User] = None
174
+ description: str | None = None
175
+ ```
176
+
177
+ ### Advanced Types
178
+
179
+ ```python
180
+ from typing import Union, Optional, Callable, Any
181
+ from decimal import Decimal
182
+
183
+ # Union for multiple types
184
+ def process_payment(amount: float, method: str) -> Union[dict, None]:
185
+ if method == "cash":
186
+ return {"change": amount - 1000}
187
+ return None
188
+
189
+ # Callable for functions as parameters
190
+ def execute_callback(callback: Callable[[str], None], message: str):
191
+ callback(message)
192
+
193
+ # TypeAlias for complex types
194
+ CartData = Dict[str, Dict[str, Union[int, str]]]
195
+
196
+ def process_cart(cart: CartData):
197
+ ...
198
+ ```
199
+
200
+ ## Common Patterns
201
+
202
+ ### Context Managers
203
+
204
+ For resources needing cleanup (files, connections, transactions):
205
+
206
+ ```python
207
+ # WRONG — no guaranteed cleanup
208
+ def write_report(data):
209
+ f = open("report.txt", "w")
210
+ f.write(data)
211
+ f.close() # Won't run if exception occurs
212
+
213
+ # CORRECT — context manager
214
+ def write_report(data):
215
+ with open("report.txt", "w") as f:
216
+ f.write(data)
217
+ # Automatic cleanup
218
+
219
+ # Custom context manager
220
+ class Transaction:
221
+ def __init__(self):
222
+ self.entered = False
223
+
224
+ def __enter__(self):
225
+ db.begin()
226
+ self.entered = True
227
+ return self
228
+
229
+ def __exit__(self, exc_type, exc_val, exc_tb):
230
+ if exc_type:
231
+ db.rollback()
232
+ else:
233
+ db.commit()
234
+ return False # Don't suppress exceptions
235
+ ```
236
+
237
+ ### Decorators
238
+
239
+ Functions that modify the behavior of other functions:
240
+
241
+ ```python
242
+ import functools
243
+ import time
244
+
245
+ # Basic decorator
246
+ def require_auth(view):
247
+ @functools.wraps(view)
248
+ def wrapper(request, *args, **kwargs):
249
+ if not request.user.is_authenticated:
250
+ return JsonResponse({"error": "Unauthorized"}, status=401)
251
+ return view(request, *args, **kwargs)
252
+ return wrapper
253
+
254
+ # Decorator with parameters
255
+ def require_role(role: str):
256
+ def decorator(view):
257
+ @functools.wraps(view)
258
+ def wrapper(request, *args, **kwargs):
259
+ if request.user.role != role:
260
+ return JsonResponse({"error": "Forbidden"}, status=403)
261
+ return view(request, *args, **kwargs)
262
+ return wrapper
263
+ return decorator
264
+
265
+ # Decorator with timing
266
+ def timing(view):
267
+ @functools.wraps(view)
268
+ def wrapper(*args, **kwargs):
269
+ start = time.perf_counter()
270
+ result = view(*args, **kwargs)
271
+ elapsed = time.perf_counter() - start
272
+ print(f"{view.__name__} took {elapsed:.4f}s")
273
+ return result
274
+ return wrapper
275
+ ```
276
+
277
+ ### Data Classes
278
+
279
+ For simple data structures:
280
+
281
+ ```python
282
+ from dataclasses import dataclass
283
+ from datetime import datetime
284
+ from decimal import Decimal
285
+
286
+ @dataclass
287
+ class CartItem:
288
+ product_id: int
289
+ quantity: int
290
+ price: Decimal
291
+ name: str = ""
292
+
293
+ @property
294
+ def subtotal(self) -> Decimal:
295
+ return self.price * self.quantity
296
+
297
+ # Usage
298
+ item = CartItem(product_id=1, quantity=2, price=Decimal("1500.00"))
299
+ print(item.subtotal) # 3000.00
300
+ ```
301
+
302
+ ### Enums
303
+
304
+ For limited values:
305
+
306
+ ```python
307
+ from enum import Enum
308
+
309
+ class OrderStatus(str, Enum):
310
+ PENDING = "pending"
311
+ PAID = "paid"
312
+ SHIPPED = "shipped"
313
+ CANCELLED = "cancelled"
314
+
315
+ @property
316
+ def is_final(self):
317
+ return self in (OrderStatus.SHIPPED, OrderStatus.CANCELLED)
318
+
319
+ # Usage
320
+ order.status = OrderStatus.PAID
321
+ if order.status.is_final:
322
+ ...
323
+ ```
324
+
325
+ ## Errors and Exceptions
326
+
327
+ ### Exception Handling
328
+
329
+ ```python
330
+ # WRONG — catch generic Exception
331
+ try:
332
+ product = Product.objects.get(id=product_id)
333
+ except Exception:
334
+ return JsonResponse({"error": "Not found"}, status=404)
335
+
336
+ # CORRECT — catch specific exception
337
+ try:
338
+ product = Product.objects.get(id=product_id)
339
+ except Product.DoesNotExist:
340
+ return JsonResponse({"error": "Product not found"}, status=404)
341
+
342
+ # With fallback
343
+ product = Product.objects.filter(id=product_id).first()
344
+ if not product:
345
+ return JsonResponse({"error": "Product not found"}, status=404)
346
+ ```
347
+
348
+ ### Custom Exceptions
349
+
350
+ ```python
351
+ class BusinessException(Exception):
352
+ """Exception for business logic errors."""
353
+ def __init__(self, message: str, code: str = "BUSINESS_ERROR"):
354
+ self.message = message
355
+ self.code = code
356
+ super().__init__(self.message)
357
+
358
+ class InsufficientStockException(BusinessException):
359
+ def __init__(self, product_id: int, requested: int, available: int):
360
+ super().__init__(
361
+ message=f"Insufficient stock: requested {requested}, available {available}",
362
+ code="INSUFFICIENT_STOCK",
363
+ )
364
+ self.product_id = product_id
365
+ self.requested = requested
366
+ self.available = available
367
+
368
+ # Usage
369
+ try:
370
+ if requested > available:
371
+ raise InsufficientStockException(product_id, requested, available)
372
+ except InsufficientStockException as e:
373
+ return JsonResponse({"error": e.message, "code": e.code}, status=400)
374
+ ```
375
+
376
+ ## Architecture Patterns
377
+
378
+ ### Repository Pattern
379
+
380
+ Abstract data access:
381
+
382
+ ```python
383
+ class ProductRepository:
384
+ @staticmethod
385
+ def get_active() -> QuerySet:
386
+ return Product.objects.filter(is_active=True)
387
+
388
+ @staticmethod
389
+ def get_by_category(category_id: int) -> QuerySet:
390
+ return Product.objects.filter(
391
+ category_id=category_id,
392
+ is_active=True,
393
+ ).select_related("category")
394
+
395
+ @staticmethod
396
+ def search(query: str) -> QuerySet:
397
+ return Product.objects.filter(
398
+ Q(name__icontains=query) | Q(sku__icontains=query),
399
+ is_active=True,
400
+ )
401
+ ```
402
+
403
+ ### Service Layer
404
+
405
+ Encapsulated business logic:
406
+
407
+ ```python
408
+ class ProductService:
409
+ @staticmethod
410
+ def get_products_for_pos(store: Store) -> List[Dict]:
411
+ products = ProductRepository.get_active().select_related(
412
+ "category", "tax"
413
+ )
414
+ return [
415
+ {
416
+ "id": p.id,
417
+ "name": p.name,
418
+ "price": str(p.get_display_price()),
419
+ "stock": p.stock if not p.is_service else None,
420
+ "is_service": p.is_service,
421
+ }
422
+ for p in products
423
+ ]
424
+
425
+ @staticmethod
426
+ def check_stock(product_id: int, quantity: int) -> bool:
427
+ try:
428
+ product = Product.objects.get(id=product_id)
429
+ except Product.DoesNotExist:
430
+ return False
431
+
432
+ if product.is_service:
433
+ return True
434
+ return product.stock >= quantity
435
+ ```
436
+
437
+ ### Value Objects
438
+
439
+ Immutable objects representing values:
440
+
441
+ ```python
442
+ from dataclasses import dataclass
443
+ from decimal import Decimal
444
+
445
+ @dataclass(frozen=True)
446
+ class Money:
447
+ amount: Decimal
448
+ currency: str = "ARS"
449
+
450
+ def __add__(self, other: "Money") -> "Money":
451
+ if self.currency != other.currency:
452
+ raise ValueError("Cannot add different currencies")
453
+ return Money(self.amount + other.amount, self.currency)
454
+
455
+ def __str__(self):
456
+ return f"{self.currency} {self.amount:.2f}"
457
+
458
+ # Usage
459
+ subtotal = Money(Decimal("1000.00"))
460
+ tax = Money(Decimal("210.00"))
461
+ total = subtotal + tax # Money(amount=Decimal('1210.00'), currency='ARS')
462
+ ```
463
+
464
+ ## Utility Functions
465
+
466
+ ### Common Helper Functions
467
+
468
+ ```python
469
+ import os
470
+ import json
471
+ from datetime import datetime
472
+ from decimal import Decimal
473
+ from typing import Any, Dict
474
+
475
+ def safe_int(value: Any, default: int = 0) -> int:
476
+ """Safely convert to int."""
477
+ try:
478
+ return int(value)
479
+ except (ValueError, TypeError):
480
+ return default
481
+
482
+ def safe_decimal(value: Any, default: Decimal = Decimal("0")) -> Decimal:
483
+ """Safely convert to Decimal."""
484
+ try:
485
+ return Decimal(str(value))
486
+ except (ValueError, TypeError):
487
+ return default
488
+
489
+ def format_money(amount: Decimal, currency: str = "ARS") -> str:
490
+ """Format money for display."""
491
+ return f"{currency} {amount:,.2f}".replace(",", ".")
492
+
493
+ def parse_json(body: bytes) -> Dict[str, Any]:
494
+ """Parse JSON from request body."""
495
+ try:
496
+ return json.loads(body)
497
+ except json.JSONDecodeError:
498
+ raise ValueError("Invalid JSON")
499
+
500
+ def truncate(text: str, length: int = 100, suffix: str = "...") -> str:
501
+ """Truncate text to length."""
502
+ if len(text) <= length:
503
+ return text
504
+ return text[:length - len(suffix)] + suffix
505
+ ```
506
+
507
+ ### Testing Utilities
508
+
509
+ ```python
510
+ # tests/test_utils.py
511
+ import pytest
512
+ from decimal import Decimal
513
+
514
+ class TestSafeInt:
515
+ def test_valid_int(self):
516
+ assert safe_int("42") == 42
517
+
518
+ def test_invalid_returns_default(self):
519
+ assert safe_int("not a number") == 0
520
+
521
+ def test_custom_default(self):
522
+ assert safe_int("invalid", 100) == 100
523
+
524
+ class TestMoney:
525
+ def test_add_same_currency(self):
526
+ a = Money(Decimal("100"))
527
+ b = Money(Decimal("50"))
528
+ assert a + b == Money(Decimal("150"))
529
+
530
+ def test_different_currency_raises(self):
531
+ a = Money(Decimal("100"), "ARS")
532
+ b = Money(Decimal("50"), "USD")
533
+ with pytest.raises(ValueError):
534
+ _ = a + b
535
+ ```
536
+
537
+ ## Code to Avoid
538
+
539
+ ### Mutable Default Arguments
540
+
541
+ ```python
542
+ # WRONG — mutable default
543
+ def add_items(items=[]):
544
+ items.append(1)
545
+ return items
546
+
547
+ # CORRECT
548
+ def add_items(items=None):
549
+ if items is None:
550
+ items = []
551
+ items.append(1)
552
+ return items
553
+ ```
554
+
555
+ ### Shadowing Builtins
556
+
557
+ ```python
558
+ # WRONG
559
+ list = [1, 2, 3] # shadows built-in
560
+ id = "abc" # shadows built-in
561
+
562
+ # CORRECT
563
+ items = [1, 2, 3]
564
+ product_id = "abc"
565
+ ```
566
+
567
+ ### Magic Numbers
568
+
569
+ ```python
570
+ # WRONG
571
+ if price > 1000:
572
+ discount = price * 0.1
573
+
574
+ # CORRECT
575
+ DISCOUNT_THRESHOLD = Decimal("1000")
576
+ DISCOUNT_RATE = Decimal("0.1")
577
+
578
+ if price > DISCOUNT_THRESHOLD:
579
+ discount = price * DISCOUNT_RATE
580
+ ```
581
+
582
+ ### Modifying Collections in Loop
583
+
584
+ ```python
585
+ # WRONG — modifies while iterating
586
+ for item in items:
587
+ if item.deleted:
588
+ items.remove(item)
589
+
590
+ # CORRECT
591
+ items = [item for item in items if not item.deleted]
592
+ # or
593
+ items_to_keep = []
594
+ for item in items:
595
+ if not item.deleted:
596
+ items_to_keep.append(item)
597
+ items = items_to_keep
598
+ ```
599
+
600
+ ## Resources
601
+
602
+ - **PEP 8** — Style Guide: <https://peps.python.org/pep-0008/>
603
+ - **PEP 484** — Type Hints: <https://peps.python.org/pep-0484/>
604
+ - **Real Python**: <https://realpython.com/>
605
+ - **Python Docs**: <https://docs.python.org/3/>