@rmrdeveloper/sideroom-pi 7.1.0 → 8.0.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.
@@ -1,26 +1,248 @@
1
- # PHP / Laravel
1
+ # PHP / Laravel Coding Guidelines
2
2
 
3
- Delta only. Keep the shared table.
3
+ Rules every PHP or Laravel change must follow.
4
4
 
5
- - Braces on every `if` / `else`.
6
- - `FormRequest` (or one equivalent boundary) is the single validator. Services trust that contract and do not re-check the same rules.
7
- - Keep Eloquent/HTTP types out of domain logic.
5
+ ## Index
6
+
7
+ | # | Rule | Tier |
8
+ | --- | ------ | ------ |
9
+ | 1 | [Braced conditionals](#1-braced-conditionals) | mechanical |
10
+ | 2 | [No magic strings](#2-no-magic-strings) | mechanical |
11
+ | 3 | [Guard clauses](#3-guard-clauses) | structural |
12
+ | 4 | [Fail fast](#4-fail-fast) | structural |
13
+ | 5 | [Explicit error handling](#5-explicit-error-handling) | structural |
14
+ | 6 | [Clear names](#6-clear-names) | structural |
15
+ | 7 | [Command Query Separation](#7-command-query-separation-cqs) | structural |
16
+ | 8 | [Null/undefined handling](#8-nullundefined-handling) | structural |
17
+ | 9 | [Immutability by default](#9-immutability-by-default) | structural |
18
+ | 10 | [Validate once](#10-validate-once) | structural |
19
+ | 11 | [Single Responsibility](#11-single-responsibility-srp) | architectural |
20
+ | 12 | [DRY (real duplication only)](#12-dry-real-duplication-only) | architectural |
21
+ | 13 | [KISS](#13-kiss) | architectural |
22
+ | 14 | [YAGNI](#14-yagni) | architectural |
23
+ | 15 | [Law of Demeter](#15-law-of-demeter) | architectural |
24
+ | 16 | [Composition over inheritance](#16-composition-over-inheritance) | architectural |
25
+ | 17 | [Dependency direction](#17-dependency-direction) | architectural |
26
+ | 18 | [Testability](#18-testability-as-a-design-constraint) | architectural |
27
+ | 19 | [Comments](#19-comments) | architectural |
28
+
29
+ ---
30
+
31
+ ### 1. Braced conditionals
32
+
33
+ RULE: Every `if`/`else` body uses braces, even a one-line guard. Never bind a statement to a conditional by indentation alone.
34
+
35
+ ```php
36
+ // bad
37
+ if ($user === null) return 0;
38
+ // good
39
+ if ($user === null) { return 0; }
40
+ ```
41
+
42
+ ### 2. No magic strings
43
+
44
+ RULE: Name domain/protocol literals once (const, enum, shared contract) and reuse the name. One-off strings with no reuse and no domain meaning may stay inline.
45
+
46
+ ```php
47
+ // bad
48
+ if ($order->status === 'pending_payment') { $this->charge($order); }
49
+ // good
50
+ if ($order->status === OrderStatus::PendingPayment) { $this->charge($order); }
51
+ ```
52
+
53
+ ### 3. Guard clauses
54
+
55
+ RULE: Validate and exit early. Keep the happy path last, at the shallowest indent. No pyramid nesting.
56
+
57
+ ```php
58
+ // bad
59
+ if ($user !== null) { if ($user->isActive()) { return $user->hasSubscription() ? .2 : 0; } } return 0;
60
+ // good
61
+ if ($user === null || !$user->isActive() || !$user->hasSubscription()) { return 0; } return .2;
62
+ ```
63
+
64
+ ### 4. Fail fast
65
+
66
+ RULE: Invalid input, impossible state, or a broken dependency throws immediately with a clear message. No fallback chains or silent defaults.
67
+ WHY: This is for invariants that should never happen if upstream contracts hold — not license to re-check business rules a boundary already validated (see rule 10).
68
+
69
+ ```php
70
+ // bad
71
+ $zone = $zones[$order->zoneId] ?? $zones['default'];
72
+ // good
73
+ $zone = $zones[$order->zoneId] ?? throw new DomainException("Unknown zoneId: {$order->zoneId}");
74
+ ```
75
+
76
+ ### 5. Explicit error handling
77
+
78
+ RULE: Never swallow an error in an empty or generic `catch`. Re-throw with context, or let it propagate.
79
+
80
+ ```php
81
+ // bad
82
+ try { return $api->getUser($id); } catch (Throwable $error) { return null; }
83
+ // good
84
+ try { return $api->getUser($id); } catch (ApiException $error) { throw new UserLoadException("Failed to load user {$id}", previous: $error); }
85
+ ```
86
+
87
+ ### 6. Clear names
88
+
89
+ RULE: Names reveal role or domain meaning. No `data`, `info`, `temp`, `result`, `obj`, `val`, `x`. A long honest name beats a short vague one.
90
+
91
+ ```php
92
+ // bad
93
+ function process(array $data): array { return array_filter($data, fn ($x) => $x->val > 0); }
94
+ // good
95
+ function activeSubscriptions(array $subscriptions): array { return array_filter($subscriptions, fn ($subscription) => $subscription->remainingDays > 0); }
96
+ ```
97
+
98
+ ### 7. Command Query Separation (CQS)
99
+
100
+ RULE: A function either does something (side effect) or returns something (query) — never both. A "getter" must never mutate.
101
+
102
+ ```php
103
+ // bad
104
+ function getNextId(Counter $counter): int { return ++$counter->value; }
105
+ // good
106
+ function peekNextId(Counter $counter): int { return $counter->value + 1; }
107
+ function incrementCounter(Counter $counter): void { ++$counter->value; }
108
+ ```
109
+
110
+ ### 8. Null/undefined handling
111
+
112
+ RULE: Pick one convention for `null` and never mix meanings. Never overload null to mean several business states — give each state its own explicit value.
113
+
114
+ ```php
115
+ // bad
116
+ function discount(?User $user): ?int { return (!$user?->plan || $user->plan->discount === 0) ? null : $user->plan->discount; }
117
+ // good
118
+ function discount(?User $user): int { return $user?->plan?->discount ?? 0; }
119
+ ```
120
+
121
+ ### 9. Immutability by default
122
+
123
+ RULE: Return new values instead of mutating parameters. Never hide a side effect inside what looks like a pure transform.
124
+
125
+ ```php
126
+ // bad
127
+ function addItem(Cart $cart, Item $item): Cart { $cart->items[] = $item; return $cart; }
128
+ // good
129
+ function addItem(Cart $cart, Item $item): Cart { return $cart->withItems([...$cart->items, $item]); }
130
+ ```
131
+
132
+ ### 10. Validate once
133
+
134
+ RULE: Validate at the boundary that owns the input (`FormRequest`, schema, DTO). Downstream code trusts that contract — no re-checking the same required/type/range rule further in.
135
+
136
+ ```php
137
+ // bad
138
+ function update(array $validated): void { if (empty($validated['email'])) { throw new InvalidArgumentException(); } $this->user->update($validated); }
139
+ // good
140
+ function update(array $validated): void { $this->user->update($validated); }
141
+ ```
142
+
143
+ ### 11. Single Responsibility (SRP)
144
+
145
+ RULE: A function, class, or module has one reason to change. If it validates AND transforms AND persists AND notifies, split it.
146
+
147
+ ```php
148
+ // bad
149
+ function save(array $input): void { $this->validate($input); $user = $this->normalize($input); $this->repo->save($user); $this->mailer->welcome($user); }
150
+ // good
151
+ function create(ValidatedUser $input): User { $user = $this->normalizer->normalize($input); $this->repo->save($user); return $user; }
152
+ ```
153
+
154
+ ### 12. DRY (real duplication only)
155
+
156
+ RULE: Extract when at least 2 of 3 hold: (a) the logic appears 3+ times, (b) a future rule change would need to touch all occurrences at once, (c) the copies represent the same domain concept. Do not extract on the first or second occurrence, or extract look-alike code from different concepts.
157
+
158
+ ```php
159
+ // bad — extracted after one use
160
+ function formatName(string $value): string { return strtoupper(trim($value)); }
161
+ // good — third occurrence confirms one domain concept
162
+ function normalizeTicketStatus(string $status): TicketStatus { return TicketStatus::from(trim($status)); }
163
+ ```
164
+
165
+ ### 13. KISS
166
+
167
+ RULE: Ship the simplest solution for the current, real requirement. No configurability, hooks, or layers added "just in case."
168
+
169
+ ```php
170
+ // bad
171
+ function formatPrice(float $value, array $options = []): string { return $this->formatter->format($value, $options); }
172
+ // good
173
+ function formatPrice(float $value): string { return '$'.number_format($value, 2); }
174
+ ```
175
+
176
+ ### 14. YAGNI
177
+
178
+ RULE: Don't add fields, params, or branches for a use case that hasn't arrived. (KISS simplifies the chosen solution; YAGNI stops you building the unrequested one.)
179
+
180
+ ```php
181
+ // bad
182
+ function createInvoice(Order $order, bool $recurring = false, bool $multiCurrency = false): Invoice { return $this->build($order, $recurring, $multiCurrency); }
183
+ // good
184
+ function createInvoice(Order $order): Invoice { return new Invoice($order->total, $order->items); }
185
+ ```
186
+
187
+ ### 15. Law of Demeter
188
+
189
+ RULE: Talk only to immediate collaborators. Never reach through an object to grab something several levels deep inside its internal structure.
190
+
191
+ ```php
192
+ // bad
193
+ function cityName(User $user): string { return $user->address->city->name; }
194
+ // good
195
+ function cityName(User $user): string { return $user->cityName(); }
196
+ ```
197
+
198
+ ### 16. Composition over inheritance
199
+
200
+ RULE: Compose small, focused behaviors instead of building inheritance chains that force unrelated methods onto every subclass.
201
+
202
+ ```php
203
+ // bad
204
+ abstract class Animal { abstract public function sound(): string; abstract public function fly(): void; }
205
+ // good
206
+ final class Dog { public function __construct(private SoundBehavior $sound) {} }
207
+ ```
208
+
209
+ ### 17. Dependency direction
210
+
211
+ RULE: Inner layers (domain/business logic) never import outer layers (framework, DB, HTTP). Outer layers depend inward, never the reverse.
8
212
 
9
213
  ```php
10
- // Bad — Service repeats the FormRequest rule
11
- class ProfileService {
12
- public function update(array $data) {
13
- if (empty($data['email'])) {
14
- throw new \InvalidArgumentException('email required');
15
- }
16
- $this->user->update($data);
17
- }
18
- }
214
+ // bad
215
+ function total(string $id): Money { return (new EloquentInvoiceRepository())->find($id)->total(); }
216
+ // good
217
+ function total(Invoice $invoice): Money { return $invoice->total(); }
218
+ ```
219
+
220
+ ### 18. Testability as a design constraint
221
+
222
+ RULE: Inject dependencies (clock, network, random) instead of hardcoding them. If a function is hard to test, redesign it — don't skip the test.
223
+
224
+ ```php
225
+ // bad
226
+ function expired(Subscription $subscription): bool { return $subscription->expiresAt < new DateTimeImmutable(); }
227
+ // good
228
+ function expired(Subscription $subscription, Clock $clock): bool { return $subscription->expiresAt < $clock->now(); }
229
+ ```
19
230
 
20
- // Good — Service trusts validated input
21
- class ProfileService {
22
- public function update(array $validated) {
23
- $this->user->update($validated);
24
- }
25
- }
231
+ ### 19. Comments
232
+
233
+ RULE: Comment only non-obvious intent — a trade-off, a constraint, a hazard names can't carry. Delete narration, restated logic, and stale TODOs. Test: if deleting the comment leaves the code equally clear, delete it.
234
+
235
+ ```php
236
+ // bad
237
+ // Increment the counter.
238
+ ++$counter;
239
+ // good
240
+ // Poll because the provider cannot guarantee single webhook delivery.
241
+ Schedule::call($checkPaymentStatus)->everyFiveSeconds();
26
242
  ```
243
+
244
+ ---
245
+
246
+ **When in doubt:** fail fast, keep it flat, keep it small.
247
+
248
+ **Enforcement note:** rules 1–2 are mechanical and should be backed by PHP/Laravel formatting or linting. Rules 3–10 are structural checks. Rules 11–19 require review judgment.
@@ -1,21 +1,321 @@
1
- # Python
1
+ # Python Coding Guidelines
2
2
 
3
- Delta only. Keep the shared table.
3
+ Rules every Python change must follow.
4
4
 
5
- - Use a complete indented suite for every `if` / `else`. Never a one-line conditional body.
6
- - Treat `None` as "not set" or "explicitly empty" — pick one meaning and keep it. Do not use `None` for a real business state that deserves its own value.
7
- - Prefer returning new collections over mutating a caller's list or dict.
5
+ ## Index
6
+
7
+ | # | Rule | Tier |
8
+ | --- | ------ | ------ |
9
+ | 1 | [Braced conditionals](#1-braced-conditionals) | mechanical |
10
+ | 2 | [No magic strings](#2-no-magic-strings) | mechanical |
11
+ | 3 | [Guard clauses](#3-guard-clauses) | structural |
12
+ | 4 | [Fail fast](#4-fail-fast) | structural |
13
+ | 5 | [Explicit error handling](#5-explicit-error-handling) | structural |
14
+ | 6 | [Clear names](#6-clear-names) | structural |
15
+ | 7 | [Command Query Separation](#7-command-query-separation-cqs) | structural |
16
+ | 8 | [Null/undefined handling](#8-nullundefined-handling) | structural |
17
+ | 9 | [Immutability by default](#9-immutability-by-default) | structural |
18
+ | 10 | [Validate once](#10-validate-once) | structural |
19
+ | 11 | [Single Responsibility](#11-single-responsibility-srp) | architectural |
20
+ | 12 | [DRY (real duplication only)](#12-dry-real-duplication-only) | architectural |
21
+ | 13 | [KISS](#13-kiss) | architectural |
22
+ | 14 | [YAGNI](#14-yagni) | architectural |
23
+ | 15 | [Law of Demeter](#15-law-of-demeter) | architectural |
24
+ | 16 | [Composition over inheritance](#16-composition-over-inheritance) | architectural |
25
+ | 17 | [Dependency direction](#17-dependency-direction) | architectural |
26
+ | 18 | [Testability](#18-testability-as-a-design-constraint) | architectural |
27
+ | 19 | [Comments](#19-comments) | architectural |
28
+
29
+ ---
30
+
31
+ ### 1. Braced conditionals
32
+
33
+ RULE: Python has no braces: every `if`/`else` body uses a full indented suite, never a one-line body.
8
34
 
9
35
  ```python
10
- # Bad
11
- def get_discount(user):
12
- if user: return 0.2 if user.is_active else 0
36
+ # bad
37
+ if user is None: return 0
38
+ # good
39
+ if user is None:
40
+ return 0
41
+ ```
42
+
43
+ ### 2. No magic strings
44
+
45
+ RULE: Name domain/protocol literals once (constant, enum, shared contract) and reuse the name. One-off strings with no reuse and no domain meaning (a single log label) may stay inline.
46
+
47
+ ```python
48
+ # bad
49
+ if order.status == "pending_payment":
50
+ charge(order)
51
+ # good
52
+ PENDING_PAYMENT = "pending_payment"
53
+ if order.status == PENDING_PAYMENT:
54
+ charge(order)
55
+ ```
56
+
57
+ ### 3. Guard clauses
58
+
59
+ RULE: Validate and exit early. Keep the happy path last, at the shallowest indent. No pyramid nesting.
13
60
 
14
- # Good
61
+ ```python
62
+ # bad
15
63
  def get_discount(user):
16
- if user is None:
17
- return 0
18
- if not user.is_active:
64
+ if user is not None:
65
+ if user.is_active:
66
+ return 0.2 if user.has_subscription else 0
67
+ return 0
68
+ # good
69
+ def get_discount(user):
70
+ if user is None or not user.is_active or not user.has_subscription:
19
71
  return 0
20
72
  return 0.2
21
73
  ```
74
+
75
+ ### 4. Fail fast
76
+
77
+ RULE: Invalid input, impossible state, or a broken dependency raises immediately with a clear message. No fallback chains or silent defaults.
78
+ WHY: This is for invariants that should never happen if upstream contracts hold — not license to re-check business rules a boundary already validated (see rule 10).
79
+
80
+ ```python
81
+ # bad
82
+ zone = zones.get(order.zone_id, zones["default"])
83
+ # good
84
+ try:
85
+ zone = zones[order.zone_id]
86
+ except KeyError as error:
87
+ raise ValueError(f"Unknown zone_id: {order.zone_id}") from error
88
+ ```
89
+
90
+ ### 5. Explicit error handling
91
+
92
+ RULE: Never swallow an error in an empty or generic `except`. Re-raise with context, or let it propagate.
93
+
94
+ ```python
95
+ # bad
96
+ def load_user(user_id):
97
+ try:
98
+ return api.get_user(user_id)
99
+ except Exception:
100
+ return None
101
+ # good
102
+ def load_user(user_id):
103
+ try:
104
+ return api.get_user(user_id)
105
+ except ApiError as error:
106
+ raise UserLoadError(f"Failed to load user {user_id}") from error
107
+ ```
108
+
109
+ ### 6. Clear names
110
+
111
+ RULE: Names reveal role or domain meaning. No `data`, `info`, `temp`, `result`, `obj`, `val`, `x`. A long honest name beats a short vague one.
112
+
113
+ ```python
114
+ # bad
115
+ def process(data):
116
+ return [x for x in data if x.val > 0]
117
+ # good
118
+ def get_active_subscriptions(subscriptions):
119
+ return [subscription for subscription in subscriptions if subscription.remaining_days > 0]
120
+ ```
121
+
122
+ ### 7. Command Query Separation (CQS)
123
+
124
+ RULE: A function either does something (side effect) or returns something (query) — never both. A "getter" must never mutate.
125
+
126
+ ```python
127
+ # bad
128
+ def get_next_id(counter):
129
+ counter.value += 1
130
+ return counter.value
131
+ # good
132
+ def peek_next_id(counter):
133
+ return counter.value + 1
134
+
135
+ def increment_counter(counter):
136
+ counter.value += 1
137
+ ```
138
+
139
+ ### 8. Null/undefined handling
140
+
141
+ RULE: Give `None` one convention (for example, not set or explicitly empty) and never use it for several meanings. Give each business state its own explicit value.
142
+
143
+ ```python
144
+ # bad
145
+ def get_discount(user):
146
+ if user is None or user.plan is None or user.plan.discount == 0:
147
+ return None
148
+ # good
149
+ def get_discount(user):
150
+ if user is None or user.plan is None:
151
+ return 0
152
+ return user.plan.discount
153
+ ```
154
+
155
+ ### 9. Immutability by default
156
+
157
+ RULE: Return new values instead of mutating parameters. Never hide a side effect inside what looks like a pure transform.
158
+
159
+ ```python
160
+ # bad
161
+ def add_item(cart, item):
162
+ cart["items"].append(item)
163
+ return cart
164
+ # good
165
+ def add_item(cart, item):
166
+ return {**cart, "items": [*cart["items"], item]}
167
+ ```
168
+
169
+ ### 10. Validate once
170
+
171
+ RULE: Validate at the boundary that owns the input (schema, request model, DTO). Downstream code trusts that contract — no re-checking the same required/type/range rule in a helper, use case, or adapter further in.
172
+
173
+ ```python
174
+ # bad
175
+ def update_profile(validated_profile, repository):
176
+ if not validated_profile.email:
177
+ raise ValueError("email required")
178
+ repository.update(validated_profile)
179
+ # good
180
+ def update_profile(validated_profile, repository):
181
+ repository.update(validated_profile)
182
+ ```
183
+
184
+ ### 11. Single Responsibility (SRP)
185
+
186
+ RULE: A function, class, or module has one reason to change. If it validates AND transforms AND persists AND notifies, split it.
187
+
188
+ ```python
189
+ # bad
190
+ def save_user(payload, repository, mailer):
191
+ if not payload["email"]:
192
+ raise ValueError("email required")
193
+ user = {**payload, "email": payload["email"].strip().lower()}
194
+ repository.insert(user)
195
+ mailer.send(user["email"], "welcome")
196
+ # good
197
+ def normalize_user(payload):
198
+ return {**payload, "email": payload["email"].strip().lower()}
199
+
200
+ def create_user(validated_payload, repository):
201
+ user = normalize_user(validated_payload)
202
+ repository.insert(user)
203
+ return user
204
+ ```
205
+
206
+ ### 12. DRY (real duplication only)
207
+
208
+ RULE: Extract when at least 2 of 3 hold: (a) the logic appears 3+ times, (b) a future rule change would need to touch all occurrences at once, (c) the copies represent the same domain concept. Do not extract on the first or second occurrence, and do not extract look-alike code that belongs to different concepts (a user's email validation vs a vendor's) — they will diverge.
209
+
210
+ ```python
211
+ # bad — extracted after one real use
212
+ def format_name(value):
213
+ return value.strip().upper()
214
+ # good — same domain concept, third occurrence confirms the pattern
215
+ def normalize_ticket_status(status):
216
+ return TicketStatus(status.strip().lower())
217
+ ```
218
+
219
+ ### 13. KISS
220
+
221
+ RULE: Ship the simplest solution for the current, real requirement. No configurability, hooks, or layers added "just in case."
222
+
223
+ ```python
224
+ # bad
225
+ def format_price(value, currency="USD", locale="en-US", show_symbol=True):
226
+ return price_formatter(value, currency, locale, show_symbol)
227
+ # good
228
+ def format_price(value):
229
+ return f"${value:.2f}"
230
+ ```
231
+
232
+ ### 14. YAGNI
233
+
234
+ RULE: Don't add fields, params, or branches for a use case that hasn't arrived. (Differs from KISS: KISS simplifies the chosen solution; YAGNI stops you building the unrequested one.)
235
+
236
+ ```python
237
+ # bad
238
+ def create_invoice(order, supports_recurring=False, supports_multi_currency=False):
239
+ return build_invoice(order, supports_recurring, supports_multi_currency)
240
+ # good
241
+ def create_invoice(order):
242
+ return {"total": order.total, "items": order.items}
243
+ ```
244
+
245
+ ### 15. Law of Demeter
246
+
247
+ RULE: Talk only to immediate collaborators. Never reach through an object to grab something several levels deep inside its internal structure.
248
+
249
+ ```python
250
+ # bad
251
+ def get_city_name(user):
252
+ return user.address.city.name
253
+ # good
254
+ def get_city_name(user):
255
+ return user.get_city_name()
256
+ ```
257
+
258
+ ### 16. Composition over inheritance
259
+
260
+ RULE: Compose small, focused behaviors instead of building inheritance chains that force unrelated methods onto every subclass.
261
+
262
+ ```python
263
+ # bad
264
+ class Animal:
265
+ def fly(self): ...
266
+ class Dog(Animal):
267
+ pass
268
+ # good
269
+ class Bark:
270
+ def make_sound(self):
271
+ return "Woof"
272
+ class Dog:
273
+ def __init__(self, sound=Bark()):
274
+ self.sound = sound
275
+ ```
276
+
277
+ ### 17. Dependency direction
278
+
279
+ RULE: Inner layers (domain/business logic) never import outer layers (framework, DB, HTTP). Outer layers depend inward, never the reverse.
280
+
281
+ ```python
282
+ # bad
283
+ def calculate_invoice_total(invoice_id):
284
+ invoice = MysqlInvoiceRepository().get(invoice_id)
285
+ return sum(item.price for item in invoice.items)
286
+ # good
287
+ def calculate_invoice_total(invoice):
288
+ return sum(item.price for item in invoice.items)
289
+ ```
290
+
291
+ ### 18. Testability as a design constraint
292
+
293
+ RULE: Inject dependencies (clock, network, random) instead of hardcoding them. If a function is hard to test, redesign it — don't skip the test.
294
+
295
+ ```python
296
+ # bad
297
+ def is_subscription_expired(subscription):
298
+ return subscription.expires_at < datetime.now(timezone.utc)
299
+ # good
300
+ def is_subscription_expired(subscription, now):
301
+ return subscription.expires_at < now
302
+ ```
303
+
304
+ ### 19. Comments
305
+
306
+ RULE: Comment only non-obvious intent — a trade-off, a constraint, a hazard names can't carry. Delete narration, restated logic, and stale TODOs. Test: if deleting the comment leaves the code equally clear, delete it.
307
+
308
+ ```python
309
+ # bad
310
+ # Increment the counter by one.
311
+ counter += 1
312
+ # good
313
+ # Polling avoids duplicate webhook delivery that the provider cannot prevent.
314
+ scheduler.every(5).seconds.do(check_payment_status)
315
+ ```
316
+
317
+ ---
318
+
319
+ **When in doubt:** fail fast, keep it flat, keep it small.
320
+
321
+ **Enforcement note:** rules 1–2 are mechanical and should be backed by Python formatting or linting. Rules 3–10 are structural checks. Rules 11–19 require review judgment.