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