mindsystem-cc 3.3.3 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +65 -1
  2. package/agents/ms-code-simplifier.md +1 -0
  3. package/agents/ms-flutter-reviewer.md +210 -0
  4. package/agents/ms-flutter-simplifier.md +42 -101
  5. package/bin/install.js +8 -0
  6. package/commands/ms/audit-milestone.md +209 -0
  7. package/commands/ms/do-work.md +7 -7
  8. package/commands/ms/execute-phase.md +5 -5
  9. package/commands/ms/research-project.md +10 -4
  10. package/mindsystem/templates/config.json +5 -1
  11. package/mindsystem/workflows/do-work.md +23 -23
  12. package/mindsystem/workflows/execute-phase.md +20 -20
  13. package/mindsystem/workflows/map-codebase.md +12 -6
  14. package/package.json +3 -2
  15. package/skills/flutter-senior-review/AGENTS.md +869 -0
  16. package/skills/flutter-senior-review/SKILL.md +205 -0
  17. package/skills/flutter-senior-review/principles/dependencies-data-not-callbacks.md +75 -0
  18. package/skills/flutter-senior-review/principles/dependencies-provider-tree.md +85 -0
  19. package/skills/flutter-senior-review/principles/dependencies-temporal-coupling.md +97 -0
  20. package/skills/flutter-senior-review/principles/pragmatism-consistent-error-handling.md +130 -0
  21. package/skills/flutter-senior-review/principles/pragmatism-speculative-generality.md +91 -0
  22. package/skills/flutter-senior-review/principles/state-data-clumps.md +64 -0
  23. package/skills/flutter-senior-review/principles/state-invalid-states.md +53 -0
  24. package/skills/flutter-senior-review/principles/state-single-source-of-truth.md +68 -0
  25. package/skills/flutter-senior-review/principles/state-type-hierarchies.md +75 -0
  26. package/skills/flutter-senior-review/principles/structure-composition-over-config.md +105 -0
  27. package/skills/flutter-senior-review/principles/structure-shared-visual-patterns.md +107 -0
  28. package/skills/flutter-senior-review/principles/structure-wrapper-pattern.md +90 -0
@@ -0,0 +1,869 @@
1
+ # Flutter Senior Review
2
+
3
+ **Version 1.0.0**
4
+ Forgeblast
5
+ January 2026
6
+
7
+ > This document is optimized for AI agents and LLMs. Principles are organized by category and prioritized by structural impact.
8
+
9
+ ---
10
+
11
+ ## Abstract
12
+
13
+ Senior engineering principles for Flutter/Dart code reviews. Uses 3 core lenses (State Modeling, Responsibility Boundaries, Abstraction Timing) backed by 12 detailed principles organized into 4 categories. Each principle includes detection signals, smell examples, senior solutions, and Dart-specific patterns. Focus is on structural improvements that make code evolvable, not just working.
14
+
15
+ ---
16
+
17
+ ## Table of Contents
18
+
19
+ 1. [Core Lenses](#1-core-lenses)
20
+ - 1.1 [State Modeling](#11-state-modeling)
21
+ - 1.2 [Responsibility Boundaries](#12-responsibility-boundaries)
22
+ - 1.3 [Abstraction Timing](#13-abstraction-timing)
23
+
24
+ 2. [State & Type Safety](#2-state--type-safety) - **CRITICAL**
25
+ - 2.1 [Make Invalid States Unrepresentable](#21-make-invalid-states-unrepresentable)
26
+ - 2.2 [Explicit Type Hierarchies](#22-explicit-type-hierarchies)
27
+ - 2.3 [Single Source of Truth](#23-single-source-of-truth)
28
+ - 2.4 [Data Clumps to Records](#24-data-clumps-to-records)
29
+
30
+ 3. [Structure & Composition](#3-structure--composition) - **HIGH**
31
+ - 3.1 [Isolate Feature Responsibility](#31-isolate-feature-responsibility)
32
+ - 3.2 [Extract Shared Visual Patterns](#32-extract-shared-visual-patterns)
33
+ - 3.3 [Composition Over Configuration](#33-composition-over-configuration)
34
+
35
+ 4. [Dependencies & Flow](#4-dependencies--flow) - **MEDIUM-HIGH**
36
+ - 4.1 [Reduce Coupling Through Data](#41-reduce-coupling-through-data)
37
+ - 4.2 [Provider Tree Architecture](#42-provider-tree-architecture)
38
+ - 4.3 [Temporal Coupling](#43-temporal-coupling)
39
+
40
+ 5. [Pragmatism](#5-pragmatism) - **MEDIUM**
41
+ - 5.1 [Speculative Generality](#51-speculative-generality)
42
+ - 5.2 [Consistent Error Handling](#52-consistent-error-handling)
43
+
44
+ ---
45
+
46
+ ## Senior Mindset
47
+
48
+ Junior and mid-level engineers ask: **"Does this code work?"**
49
+ Senior engineers ask: **"How will this code change? What happens when requirements shift?"**
50
+
51
+ This distinction drives everything. Code that "works" today becomes a liability when:
52
+ - A new state is added and 5 files need coordinated updates
53
+ - A feature toggle requires touching code scattered across the codebase
54
+ - A bug fix in one place breaks assumptions elsewhere
55
+
56
+ Focus on **structural issues that compound over time** - the kind that turn "add a simple feature" into "refactor half the codebase first."
57
+
58
+ ---
59
+
60
+ ## 1. Core Lenses
61
+
62
+ Apply these three lenses to every review. They catch 80% of structural issues.
63
+
64
+ ### 1.1 State Modeling
65
+
66
+ **Question:** Can this code represent invalid states?
67
+
68
+ Look for:
69
+ - Multiple boolean flags (2^n possible states, many invalid)
70
+ - Primitive obsession (stringly-typed status, magic numbers)
71
+ - Same decision logic repeated in multiple places
72
+
73
+ **Senior pattern:** Sealed classes where each variant is a valid state. Factory methods that encapsulate decision logic. Compiler-enforced exhaustive handling.
74
+
75
+ ---
76
+
77
+ ### 1.2 Responsibility Boundaries
78
+
79
+ **Question:** If I remove/modify feature X, how many files change?
80
+
81
+ Look for:
82
+ - Optional feature logic scattered throughout a parent component
83
+ - Widgets with 6+ parameters (doing too much)
84
+ - Deep callback chains passing flags through layers
85
+
86
+ **Senior pattern:** Wrapper components for optional features. Typed data objects instead of flag parades. Each widget has one job.
87
+
88
+ ---
89
+
90
+ ### 1.3 Abstraction Timing
91
+
92
+ **Question:** Is this abstraction earned or speculative?
93
+
94
+ Look for:
95
+ - Interfaces with only one implementation
96
+ - Factories that create only one type
97
+ - "Flexible" config that's never varied
98
+ - BUT ALSO: Duplicated code that should be unified
99
+
100
+ **Senior pattern:** Abstract when you have 2-3 concrete cases, not before. Extract when duplication causes bugs or drift, not for aesthetics.
101
+
102
+ ---
103
+
104
+ ## 2. State & Type Safety
105
+
106
+ **Impact: CRITICAL**
107
+
108
+ Issues with state modeling compound rapidly. Invalid state combinations cause bugs that are hard to reproduce and fix.
109
+
110
+ ### 2.1 Make Invalid States Unrepresentable
111
+
112
+ **Impact: CRITICAL (Eliminates entire class of bugs)**
113
+
114
+ Multiple boolean flags that create 2^n possible states, where many combinations are invalid or nonsensical.
115
+
116
+ **Detection signals:**
117
+ - 3+ boolean parameters passed together
118
+ - Same boolean checks repeated in multiple places
119
+ - if/else chains checking flag combinations
120
+ - Some flag combinations would cause undefined behavior
121
+
122
+ **Incorrect (boolean flag explosion):**
123
+
124
+ ```dart
125
+ Widget build() {
126
+ final isLoading = ...;
127
+ final isExpired = ...;
128
+ final isTutorial = ...;
129
+ final hasBonus = ...;
130
+ // What happens when isTutorial && isExpired?
131
+ // What about isLoading && hasBonus && isTutorial?
132
+ }
133
+ ```
134
+
135
+ **Correct (sealed class hierarchy):**
136
+
137
+ ```dart
138
+ sealed class ItemMode {
139
+ const ItemMode();
140
+ }
141
+ final class ItemModeNormal extends ItemMode { ... }
142
+ final class ItemModeTutorial extends ItemMode { ... }
143
+ final class ItemModeExpired extends ItemMode { ... }
144
+ ```
145
+
146
+ **Why it matters:**
147
+ - Compiler enforces exhaustive handling via switch expressions
148
+ - New states added explicitly, not as boolean combinations
149
+ - Impossible to create invalid state combinations
150
+ - Self-documenting: sealed class shows all possible states
151
+
152
+ ---
153
+
154
+ ### 2.2 Explicit Type Hierarchies
155
+
156
+ **Impact: HIGH (Centralizes decision logic)**
157
+
158
+ Complex if/else chains determining behavior scattered across widgets rather than encoded in the type system.
159
+
160
+ **Detection signals:**
161
+ - Complex if/else chains determining which widget to render
162
+ - Same decision logic duplicated across multiple widgets
163
+ - Widgets receive data they only use to make decisions (not to display)
164
+ - New requirement would add another boolean parameter
165
+
166
+ **Incorrect (scattered decision logic):**
167
+
168
+ ```dart
169
+ Widget _buildTrailing(BuildContext context, {required bool isExpired, required bool isTutorial}) {
170
+ if (quest.status.isClaimable && quest.isExpired) {
171
+ return QuestClaimButton(quest: quest, isExpired: true);
172
+ }
173
+ if (quest.canClaim) {
174
+ if (isTutorial) {
175
+ return PulsingGlowWrapper(child: QuestClaimButton(quest: quest, isTutorial: true));
176
+ }
177
+ return QuestClaimButton(quest: quest);
178
+ }
179
+ return _buildRewardBadge(context);
180
+ }
181
+ ```
182
+
183
+ **Correct (factory with type hierarchy):**
184
+
185
+ ```dart
186
+ sealed class QuestClaimMode {
187
+ factory QuestClaimMode.fromContext({
188
+ required QuestEntity quest,
189
+ required UserEntity? user,
190
+ required bool isTutorialQuest,
191
+ }) {
192
+ if (quest.status.isClaimable && quest.isExpired) {
193
+ return QuestClaimModeExpired(...);
194
+ }
195
+ if (quest.canClaim) {
196
+ return isTutorialQuest
197
+ ? QuestClaimModeTutorial(...)
198
+ : QuestClaimModeNormal(...);
199
+ }
200
+ return QuestClaimModePending(...);
201
+ }
202
+ }
203
+
204
+ // Widget becomes a simple switch
205
+ Widget _buildTrailing() => switch (claimMode) {
206
+ QuestClaimModePending() => QuestRewardDisplay(...),
207
+ QuestClaimModeTutorial() => _wrapWithTutorialEffects(QuestClaimButton(mode: claimMode)),
208
+ QuestClaimModeNormal() || QuestClaimModeExpired() => QuestClaimButton(mode: claimMode),
209
+ };
210
+ ```
211
+
212
+ **Why it matters:**
213
+ - Decision logic lives in one place (the factory)
214
+ - Widgets receive pre-computed decisions, not raw data to interpret
215
+ - Adding new modes is explicit and compiler-checked
216
+ - Testing is clearer: test the factory, then test each mode's rendering
217
+
218
+ ---
219
+
220
+ ### 2.3 Single Source of Truth
221
+
222
+ **Impact: HIGH (Prevents stale data bugs)**
223
+
224
+ Same state tracked in multiple places - local useState duplicating provider state, widgets caching derived values.
225
+
226
+ **Detection signals:**
227
+ - Same data stored in both a provider and local widget state
228
+ - useEffect hooks syncing local state from provider state
229
+ - Two sources of truth could disagree
230
+ - Derived data being cached instead of computed
231
+
232
+ **Incorrect (duplicated state):**
233
+
234
+ ```dart
235
+ class ItemScreen extends HookConsumerWidget {
236
+ Widget build(context, ref) {
237
+ final items = ref.watch(itemsProvider);
238
+ // Local state duplicating what provider knows
239
+ final selectedItem = useState<Item?>(null);
240
+ final isEditing = useState(false);
241
+
242
+ // Widget caches a derived value
243
+ final totalPrice = useState(0);
244
+ useEffect(() {
245
+ totalPrice.value = items.fold(0, (sum, i) => sum + i.price);
246
+ }, [items]);
247
+ }
248
+ }
249
+ ```
250
+
251
+ **Correct (single owner, derived values):**
252
+
253
+ ```dart
254
+ // Provider owns the state
255
+ @riverpod
256
+ class ItemsController extends _$ItemsController {
257
+ Item? get selectedItem => state.value?.firstWhereOrNull((i) => i.isSelected);
258
+ int get totalPrice => state.value?.fold(0, (sum, i) => sum + i.price) ?? 0;
259
+ }
260
+
261
+ // Widget only has ephemeral UI state
262
+ class ItemScreen extends HookConsumerWidget {
263
+ Widget build(context, ref) {
264
+ final items = ref.watch(itemsProvider);
265
+ final selectedItem = ref.watch(itemsProvider.select((s) => s.selectedItem));
266
+ final isTextFieldFocused = useState(false); // Truly ephemeral
267
+ }
268
+ }
269
+ ```
270
+
271
+ **Why it matters:**
272
+ - No "which value is authoritative?" confusion
273
+ - State updates propagate automatically
274
+ - Easier debugging: one place to inspect
275
+ - Prevents stale data bugs
276
+
277
+ ---
278
+
279
+ ### 2.4 Data Clumps to Records
280
+
281
+ **Impact: MEDIUM-HIGH (Reduces parameter proliferation)**
282
+
283
+ Same 3-4 parameters appear together repeatedly across methods and constructors.
284
+
285
+ **Detection signals:**
286
+ - Same 3+ parameters appear in multiple function signatures
287
+ - Parameters are logically related (always used together)
288
+ - Adding a new related field requires updating many signatures
289
+ - Bugs from passing parameters in the wrong order
290
+
291
+ **Incorrect (repeated parameter groups):**
292
+
293
+ ```dart
294
+ void showReward(int baseReward, int? boostedReward, int? multiplier) { ... }
295
+ void displayBadge(int baseReward, int? boostedReward, int? multiplier) { ... }
296
+ void logClaim(int baseReward, int? boostedReward, int? multiplier) { ... }
297
+ ```
298
+
299
+ **Correct (typed record/class):**
300
+
301
+ ```dart
302
+ // Record for simple data grouping
303
+ typedef RewardBonus = ({int base, int? boosted, int? multiplier});
304
+
305
+ // Or class if behavior is needed
306
+ class RewardCalculation {
307
+ final int base;
308
+ final int? boosted;
309
+ final int? multiplier;
310
+
311
+ bool get hasBonus => multiplier != null;
312
+ int get displayAmount => boosted ?? base;
313
+ }
314
+
315
+ // Clean call sites
316
+ void showReward(RewardBonus bonus) { ... }
317
+ ```
318
+
319
+ **Why it matters:**
320
+ - Single place to add new related fields
321
+ - Impossible to pass parameters in wrong order
322
+ - Semantic meaning is clear
323
+ - Reduces parameter count everywhere
324
+
325
+ ---
326
+
327
+ ## 3. Structure & Composition
328
+
329
+ **Impact: HIGH**
330
+
331
+ Structural issues make features hard to add, remove, or modify independently.
332
+
333
+ ### 3.1 Isolate Feature Responsibility
334
+
335
+ **Impact: HIGH (Features become removable)**
336
+
337
+ A feature's logic is woven throughout a parent component rather than encapsulated.
338
+
339
+ **Detection signals:**
340
+ - More than 30% of a widget's code dedicated to one optional feature
341
+ - Removing a feature requires deleting scattered lines throughout the file
342
+ - Multiple `if (featureEnabled)` checks spread across the widget
343
+ - State variables only used by one feature
344
+
345
+ **Incorrect (scattered feature logic):**
346
+
347
+ ```dart
348
+ class QuestListScreen extends HookConsumerWidget {
349
+ Widget build(context, ref) {
350
+ // 50 lines of tutorial-specific state management
351
+ final tutorialKey = useMemoized(GlobalKey.new);
352
+ final overlayVisible = useState(true);
353
+ final cutoutRect = useState<Rect?>(null);
354
+
355
+ useEffect(() {
356
+ if (isTutorialActive) {
357
+ // 30 lines of tutorial position measurement
358
+ }
359
+ });
360
+
361
+ return Stack(
362
+ children: [
363
+ questList,
364
+ if (showTutorialOverlay) TutorialOverlay(...),
365
+ ],
366
+ );
367
+ }
368
+ }
369
+ ```
370
+
371
+ **Correct (wrapper pattern):**
372
+
373
+ ```dart
374
+ // Tutorial logic fully encapsulated
375
+ class TutorialQuestSpotlight extends HookConsumerWidget {
376
+ final Widget Function(BuildContext, {GlobalKey? tutorialKey}) builder;
377
+
378
+ Widget build(context, ref) {
379
+ // All tutorial state lives here
380
+ // Core list doesn't know tutorials exist
381
+ }
382
+ }
383
+
384
+ // Clean core component
385
+ class QuestListScreen extends HookConsumerWidget {
386
+ Widget build(context, ref) {
387
+ return TutorialQuestSpotlight(
388
+ builder: (context, {tutorialKey}) => _buildQuestList(tutorialKey),
389
+ );
390
+ }
391
+ }
392
+ ```
393
+
394
+ **Why it matters:**
395
+ - Feature can be disabled/removed by removing one wrapper
396
+ - Core component remains focused and testable
397
+ - Feature logic is cohesive and isolated
398
+ - Multiple features can compose without polluting each other
399
+
400
+ ---
401
+
402
+ ### 3.2 Extract Shared Visual Patterns
403
+
404
+ **Impact: MEDIUM-HIGH (Guarantees consistency)**
405
+
406
+ Similar UI code duplicated across widgets with minor variations and subtle inconsistencies.
407
+
408
+ **Detection signals:**
409
+ - Similar Container/decoration patterns across multiple widgets
410
+ - Visual elements (colors, padding, borders) vary based on state
411
+ - Design change would require updating multiple files
412
+ - Subtle inconsistencies between similar UI elements
413
+
414
+ **Incorrect (duplicated patterns):**
415
+
416
+ ```dart
417
+ // In QuestClaimButton
418
+ Container(
419
+ padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
420
+ decoration: BoxDecoration(
421
+ color: isExpired ? AppColors.gray400 : AppColors.forge,
422
+ borderRadius: BorderRadius.circular(20),
423
+ ),
424
+ child: Row(children: [Text('grape'), Text('$reward')]),
425
+ )
426
+
427
+ // In QuestListTile (slightly different)
428
+ Container(
429
+ padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
430
+ decoration: BoxDecoration(
431
+ borderRadius: BorderRadius.circular(20),
432
+ border: Border.all(color: ...),
433
+ ),
434
+ child: Row(children: [Text('grape'), Text('$reward')]),
435
+ )
436
+ ```
437
+
438
+ **Correct (shared widget with variants):**
439
+
440
+ ```dart
441
+ enum QuestRewardStyle { filled, outlined, disabled }
442
+
443
+ class QuestRewardDisplay extends StatelessWidget {
444
+ final int reward;
445
+ final QuestRewardStyle style;
446
+
447
+ Widget build(context) {
448
+ return Container(
449
+ decoration: style.buildDecoration(context),
450
+ child: Row(children: [Text('grape'), Text('$reward', style: style.buildTextStyle(context))]),
451
+ );
452
+ }
453
+ }
454
+
455
+ extension on QuestRewardStyle {
456
+ BoxDecoration buildDecoration(BuildContext context) => switch (this) {
457
+ QuestRewardStyle.filled => BoxDecoration(color: AppColors.forge, ...),
458
+ QuestRewardStyle.outlined => BoxDecoration(border: Border.all(...), ...),
459
+ QuestRewardStyle.disabled => BoxDecoration(color: AppColors.gray400, ...),
460
+ };
461
+ }
462
+ ```
463
+
464
+ **Why it matters:**
465
+ - Visual consistency is guaranteed
466
+ - Style changes propagate automatically
467
+ - New variants are added in one place
468
+ - Reduces widget file sizes significantly
469
+
470
+ ---
471
+
472
+ ### 3.3 Composition Over Configuration
473
+
474
+ **Impact: MEDIUM (Simplifies widget APIs)**
475
+
476
+ Giant widgets with dozens of boolean flags - "god widgets" that handle every case through configuration.
477
+
478
+ **Detection signals:**
479
+ - Widget has more than 6-8 parameters
480
+ - Boolean parameters that are mutually exclusive
481
+ - Widget is really 3 different widgets pretending to be 1
482
+ - Adding a new variant would require another boolean flag
483
+
484
+ **Incorrect (god widget):**
485
+
486
+ ```dart
487
+ AppButton(
488
+ label: 'Submit',
489
+ isPrimary: true,
490
+ isSecondary: false,
491
+ isDestructive: false,
492
+ isLoading: false,
493
+ isDisabled: false,
494
+ showIcon: true,
495
+ iconPosition: IconPosition.left,
496
+ size: ButtonSize.medium,
497
+ // ... 10 more parameters
498
+ )
499
+ ```
500
+
501
+ **Correct (composed widgets):**
502
+
503
+ ```dart
504
+ // Composition of focused widgets
505
+ PrimaryButton(
506
+ onPressed: handleSubmit,
507
+ child: Row(
508
+ children: [
509
+ Icon(Icons.check),
510
+ SizedBox(width: 8),
511
+ Text('Submit'),
512
+ ],
513
+ ),
514
+ )
515
+
516
+ // Or slot-based API for common patterns
517
+ PrimaryButton.icon(
518
+ icon: Icons.check,
519
+ label: 'Submit',
520
+ onPressed: handleSubmit,
521
+ )
522
+ ```
523
+
524
+ **Why it matters:**
525
+ - Each widget has one job
526
+ - New variants don't bloat existing widgets
527
+ - Easier to understand and test
528
+ - Flexible: compose for custom needs, use shortcuts for common ones
529
+
530
+ ---
531
+
532
+ ## 4. Dependencies & Flow
533
+
534
+ **Impact: MEDIUM-HIGH**
535
+
536
+ Coupling and dependency issues make code changes ripple unexpectedly.
537
+
538
+ ### 4.1 Reduce Coupling Through Data
539
+
540
+ **Impact: MEDIUM-HIGH (Stabilizes APIs)**
541
+
542
+ Parent widgets pass many callbacks and control flags to children, creating tight coupling.
543
+
544
+ **Detection signals:**
545
+ - Widgets have 4+ parameters beyond key and callbacks
546
+ - Boolean flags being passed through multiple widget layers
547
+ - Changing a child's behavior requires changing the parent's call site
548
+ - Parameters that are only used in some conditions
549
+
550
+ **Incorrect (flag parade):**
551
+
552
+ ```dart
553
+ QuestClaimButton(
554
+ quest: quest,
555
+ onSuccess: onClaimSuccess,
556
+ isExpired: isExpired,
557
+ isTutorial: isTutorial,
558
+ bonus: bonus,
559
+ )
560
+ ```
561
+
562
+ **Correct (typed data object):**
563
+
564
+ ```dart
565
+ QuestClaimButton(
566
+ mode: QuestClaimMode.fromContext(quest: quest, user: user, isTutorial: isTutorial),
567
+ onSuccess: onClaimSuccess,
568
+ )
569
+ ```
570
+
571
+ **Why it matters:**
572
+ - Child widget's API is stable even as requirements change
573
+ - Parent doesn't need to know child's internal decision logic
574
+ - Data dependencies are explicit in the mode type
575
+ - Easier to test: create mode objects directly
576
+
577
+ ---
578
+
579
+ ### 4.2 Provider Tree Architecture
580
+
581
+ **Impact: MEDIUM (Clarifies data flow)**
582
+
583
+ Flat provider structure where many providers read from many others with no clear hierarchy.
584
+
585
+ **Mental model:**
586
+ - **Root providers**: Match app lifecycle, never disposed. Hold core entities (user, games, quests). Referenced from many places.
587
+ - **Branch providers**: Combine/filter/transform core data. May become "core" as app grows.
588
+ - **Leaf providers**: Screen-specific or ephemeral. Depend on branches, rarely watched by other providers.
589
+
590
+ **Detection signals:**
591
+ - Can't draw the provider dependency graph as a tree
592
+ - Circular or confusing dependency chains
593
+ - "Leaf" providers being watched by other providers
594
+ - Provider doing too much (should split into root + branch)
595
+
596
+ **Correct (tree structure):**
597
+
598
+ ```dart
599
+ // Root (core entities, keepAlive: true)
600
+ @Riverpod(keepAlive: true)
601
+ Future<User> user(Ref ref) => ...;
602
+
603
+ @Riverpod(keepAlive: true)
604
+ Future<List<Quest>> quests(Ref ref) => ...;
605
+
606
+ // Branch (derived/filtered, may become core as app grows)
607
+ @riverpod
608
+ Future<List<Quest>> squadQuests(Ref ref) async {
609
+ final quests = await ref.watch(questsProvider.future);
610
+ return quests.where((q) => q.type == QuestType.squad).toList();
611
+ }
612
+
613
+ // Leaf (screen-specific, ephemeral)
614
+ @riverpod
615
+ class SquadQuestScreenState extends _$SquadQuestScreenState {
616
+ // Only watches branch providers, never watched by others
617
+ }
618
+ ```
619
+
620
+ **Why it matters:**
621
+ - Clear mental model of data flow
622
+ - Predictable rebuild scope
623
+ - Easier to add new derived providers
624
+ - Natural place for each piece of logic
625
+
626
+ ---
627
+
628
+ ### 4.3 Temporal Coupling
629
+
630
+ **Impact: MEDIUM (Catches misuse at compile time)**
631
+
632
+ Operations must happen in a specific order, but nothing enforces that order.
633
+
634
+ **Detection signals:**
635
+ - Methods that must be called before others
636
+ - Comments like "must call X first" or "call after Y"
637
+ - Objects can be in an "invalid" state between operations
638
+ - Tests have setup steps that could be forgotten
639
+
640
+ **Incorrect (implicit sequence):**
641
+
642
+ ```dart
643
+ class PaymentProcessor {
644
+ void init() { ... }
645
+ void setAmount(int amount) { ... }
646
+ void setCustomer(Customer c) { ... }
647
+ Future<void> process() { ... } // Must call init, setAmount, setCustomer first!
648
+ }
649
+
650
+ // Easy to misuse:
651
+ final processor = PaymentProcessor();
652
+ processor.process(); // Boom - forgot to init
653
+ ```
654
+
655
+ **Correct (type-enforced sequence):**
656
+
657
+ ```dart
658
+ // Builder pattern enforces sequence
659
+ class PaymentBuilder {
660
+ PaymentBuilder withAmount(int amount) => ...;
661
+ PaymentBuilder withCustomer(Customer c) => ...;
662
+ Payment build() => ...; // Only callable when all required fields set
663
+ }
664
+
665
+ // Or use types to enforce state
666
+ class UninitializedProcessor { InitializedProcessor init() => ...; }
667
+ class InitializedProcessor { Future<Result> process(int amount, Customer c) => ...; }
668
+ ```
669
+
670
+ **Why it matters:**
671
+ - Compiler catches misuse, not runtime
672
+ - Self-documenting: types show valid sequences
673
+ - Impossible to forget required steps
674
+ - Easier onboarding for new developers
675
+
676
+ ---
677
+
678
+ ## 5. Pragmatism
679
+
680
+ **Impact: MEDIUM**
681
+
682
+ Over-engineering and inconsistency create unnecessary complexity.
683
+
684
+ ### 5.1 Speculative Generality
685
+
686
+ **Impact: MEDIUM (Reduces unnecessary complexity)**
687
+
688
+ Abstractions added for hypothetical future needs that may never materialize.
689
+
690
+ **Detection signals:**
691
+ - Interface with only one implementation
692
+ - Factory that only creates one type
693
+ - Configuration options no one uses
694
+ - Abstraction added "in case we need it later"
695
+
696
+ **Incorrect (premature abstraction):**
697
+
698
+ ```dart
699
+ // "We might need different storage backends someday"
700
+ abstract class StorageStrategy { ... }
701
+ class LocalStorageStrategy implements StorageStrategy { ... }
702
+ class StorageFactory {
703
+ static StorageStrategy create(StorageType type) => switch (type) {
704
+ StorageType.local => LocalStorageStrategy(), // Only one ever used
705
+ };
706
+ }
707
+ ```
708
+
709
+ **Correct (direct usage):**
710
+
711
+ ```dart
712
+ // Just use the thing directly
713
+ class LocalStorage {
714
+ Future<void> save(String key, String value) => ...;
715
+ Future<String?> load(String key) => ...;
716
+ }
717
+
718
+ // When you ACTUALLY need a second implementation, THEN abstract
719
+ // The refactoring is straightforward and you'll know the right abstraction
720
+ ```
721
+
722
+ **Why it matters:**
723
+ - Less code to maintain
724
+ - Abstractions based on real needs fit better
725
+ - Easier to understand: no indirection to trace
726
+ - YAGNI: You Aren't Gonna Need It
727
+
728
+ ---
729
+
730
+ ### 5.2 Consistent Error Handling
731
+
732
+ **Impact: MEDIUM (Improves UX and debugging)**
733
+
734
+ Ad-hoc try/catch scattered in screens, inconsistent error UX across the app.
735
+
736
+ **Detection signals:**
737
+ - Errors appear differently across screens (toasts vs dialogs vs inline)
738
+ - try/catch blocks scattered in widget code
739
+ - Inconsistent error messaging
740
+ - No standard retry mechanism
741
+
742
+ **Incorrect (ad-hoc handling):**
743
+
744
+ ```dart
745
+ // Screen 1: Try/catch with toast
746
+ try {
747
+ await ref.read(saveProvider.notifier).save();
748
+ } catch (e) {
749
+ ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('$e')));
750
+ }
751
+
752
+ // Screen 2: Different pattern
753
+ final result = await ref.read(saveProvider.notifier).save();
754
+ if (result.isError) {
755
+ showDialog(...); // Different error UI
756
+ }
757
+
758
+ // Screen 3: No error handling at all
759
+ await ref.read(saveProvider.notifier).save(); // Hope it works!
760
+ ```
761
+
762
+ **Correct (consistent pattern):**
763
+
764
+ ```dart
765
+ // Providers handle errors uniformly
766
+ @riverpod
767
+ class SaveController extends _$SaveController {
768
+ Future<void> save() async {
769
+ state = const AsyncLoading();
770
+ state = await AsyncValue.guard(() => _repository.save());
771
+ // Error stays in state, not thrown
772
+ }
773
+ }
774
+
775
+ // Screens use consistent pattern
776
+ Widget build(context, ref) {
777
+ // Centralized error listening
778
+ ref.listenOnError(saveProvider);
779
+
780
+ final saveState = ref.watch(saveProvider);
781
+
782
+ return AppPrimaryButton(
783
+ isLoading: saveState.isLoading,
784
+ onPressed: () => ref.read(saveProvider.notifier).save(),
785
+ child: Text('Save'),
786
+ );
787
+ }
788
+ ```
789
+
790
+ **Why it matters:**
791
+ - Users get consistent experience
792
+ - Developers follow one pattern
793
+ - Error states are explicit and testable
794
+ - Retry logic is standardized
795
+
796
+ ---
797
+
798
+ ## Context Gathering
799
+
800
+ When asked to review code, first identify the target:
801
+
802
+ If target is unclear, ask:
803
+ - What code should be reviewed? (specific files, a feature folder, uncommitted changes, a commit, a patch file)
804
+ - Is there a specific concern or area of focus?
805
+
806
+ For the specified target, gather the relevant code:
807
+ - **Commit**: `git show <commit>`
808
+ - **Patch file**: Read the patch file
809
+ - **Uncommitted changes**: `git diff` and `git diff --cached`
810
+ - **Folder/feature**: Read the relevant files in that directory
811
+ - **Specific file**: Read that file and related files it imports/uses
812
+
813
+ ---
814
+
815
+ ## Analysis Process
816
+
817
+ 1. **Read thoroughly** - Understand what the code does, not just its structure
818
+
819
+ 2. **Apply the three lenses** - For each lens, note specific instances (or note "no issues found")
820
+
821
+ 3. **Check for additional patterns** - If you notice issues beyond the core lenses, consult the principle sections for precise diagnosis
822
+
823
+ 4. **Prioritize by evolution impact**:
824
+ - High: Will cause cascading changes when requirements shift
825
+ - Medium: Creates friction but contained to one area
826
+ - Low: Suboptimal but won't compound
827
+
828
+ 5. **Formulate concrete suggestions** - Name specific extractions, show before/after for the highest-impact change
829
+
830
+ ---
831
+
832
+ ## Output Format
833
+
834
+ ```markdown
835
+ ## Senior Review: [Target]
836
+
837
+ ### Summary
838
+ [1-2 sentences: Overall assessment and the single most important structural opportunity]
839
+
840
+ ### Findings
841
+
842
+ #### High Impact: [Issue Name]
843
+ **What I noticed:** [Specific code pattern observed]
844
+ **Why it matters:** [How this will cause problems as code evolves]
845
+ **Suggestion:** [Concrete refactoring - name the types/widgets to extract]
846
+
847
+ #### Medium Impact: [Issue Name]
848
+ [Same structure]
849
+
850
+ #### Low Impact: [Issue Name]
851
+ [Same structure]
852
+
853
+ ### No Issues Found
854
+ [If a lens revealed no problems, briefly note: "State modeling: No boolean flag combinations or repeated decision logic detected."]
855
+
856
+ ---
857
+
858
+ **What's your take on these suggestions? Any context I'm missing?**
859
+ ```
860
+
861
+ ---
862
+
863
+ ## Success Criteria
864
+
865
+ - At least one finding per applicable lens (or explicit "no issues" statement)
866
+ - Each finding tied to evolution impact, not just "could be better"
867
+ - Suggestions are concrete: specific types/widgets named, not vague advice
868
+ - No forced findings - if code is solid, say so
869
+ - User has opportunity to provide context before changes