mindsystem-cc 3.21.0 → 3.22.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 (87) hide show
  1. package/README.md +4 -12
  2. package/agents/ms-debugger.md +196 -880
  3. package/agents/ms-plan-checker.md +30 -30
  4. package/agents/ms-plan-writer.md +1 -1
  5. package/agents/ms-product-researcher.md +4 -2
  6. package/agents/ms-verifier.md +25 -117
  7. package/commands/ms/add-phase.md +3 -4
  8. package/commands/ms/add-todo.md +3 -4
  9. package/commands/ms/adhoc.md +3 -4
  10. package/commands/ms/audit-milestone.md +4 -3
  11. package/commands/ms/complete-milestone.md +2 -2
  12. package/commands/ms/config.md +36 -9
  13. package/commands/ms/create-roadmap.md +3 -4
  14. package/commands/ms/debug.md +27 -28
  15. package/commands/ms/design-phase.md +8 -5
  16. package/commands/ms/discuss-phase.md +2 -2
  17. package/commands/ms/doctor.md +9 -6
  18. package/commands/ms/execute-phase.md +2 -5
  19. package/commands/ms/help.md +2 -2
  20. package/commands/ms/insert-phase.md +3 -4
  21. package/commands/ms/map-codebase.md +1 -2
  22. package/commands/ms/new-milestone.md +1 -3
  23. package/commands/ms/new-project.md +3 -5
  24. package/commands/ms/plan-milestone-gaps.md +3 -4
  25. package/commands/ms/plan-phase.md +2 -3
  26. package/commands/ms/progress.md +1 -0
  27. package/commands/ms/remove-phase.md +3 -4
  28. package/commands/ms/research-phase.md +4 -4
  29. package/commands/ms/research-project.md +9 -16
  30. package/commands/ms/review-design.md +4 -2
  31. package/commands/ms/verify-work.md +6 -8
  32. package/mindsystem/templates/config.json +2 -1
  33. package/mindsystem/templates/roadmap.md +1 -1
  34. package/mindsystem/templates/state.md +2 -2
  35. package/mindsystem/templates/verification-report.md +3 -26
  36. package/mindsystem/workflows/diagnose-issues.md +0 -1
  37. package/mindsystem/workflows/discuss-phase.md +7 -3
  38. package/mindsystem/workflows/execute-phase.md +2 -18
  39. package/mindsystem/workflows/map-codebase.md +6 -12
  40. package/mindsystem/workflows/mockup-generation.md +46 -22
  41. package/mindsystem/workflows/plan-phase.md +12 -5
  42. package/mindsystem/workflows/verify-work.md +96 -69
  43. package/package.json +1 -1
  44. package/scripts/__pycache__/ms-tools.cpython-314.pyc +0 -0
  45. package/scripts/__pycache__/test_ms_tools.cpython-314-pytest-9.0.2.pyc +0 -0
  46. package/scripts/ms-tools.py +751 -6
  47. package/scripts/test_ms_tools.py +786 -0
  48. package/skills/senior-review/AGENTS.md +531 -0
  49. package/skills/{flutter-senior-review → senior-review}/SKILL.md +47 -36
  50. package/skills/senior-review/principles/dependencies-api-boundary-design.md +32 -0
  51. package/skills/senior-review/principles/dependencies-data-not-flags.md +32 -0
  52. package/skills/senior-review/principles/dependencies-temporal-coupling.md +32 -0
  53. package/skills/senior-review/principles/pragmatism-consistent-error-handling.md +32 -0
  54. package/skills/senior-review/principles/pragmatism-speculative-generality.md +32 -0
  55. package/skills/senior-review/principles/state-invalid-states.md +33 -0
  56. package/skills/senior-review/principles/state-single-source-of-truth.md +32 -0
  57. package/skills/senior-review/principles/state-type-hierarchies.md +32 -0
  58. package/skills/senior-review/principles/structure-composition-over-config.md +32 -0
  59. package/skills/senior-review/principles/structure-feature-isolation.md +32 -0
  60. package/skills/senior-review/principles/structure-module-cohesion.md +32 -0
  61. package/agents/ms-flutter-code-quality.md +0 -169
  62. package/agents/ms-flutter-reviewer.md +0 -211
  63. package/agents/ms-flutter-simplifier.md +0 -79
  64. package/mindsystem/references/debugging/debugging-mindset.md +0 -11
  65. package/mindsystem/references/debugging/hypothesis-testing.md +0 -11
  66. package/mindsystem/references/debugging/investigation-techniques.md +0 -11
  67. package/mindsystem/references/debugging/verification-patterns.md +0 -11
  68. package/mindsystem/references/debugging/when-to-research.md +0 -11
  69. package/mindsystem/references/git-integration.md +0 -254
  70. package/mindsystem/references/verification-patterns.md +0 -595
  71. package/mindsystem/workflows/debug.md +0 -14
  72. package/mindsystem/workflows/verify-phase.md +0 -625
  73. package/skills/flutter-code-quality/SKILL.md +0 -143
  74. package/skills/flutter-code-simplification/SKILL.md +0 -102
  75. package/skills/flutter-senior-review/AGENTS.md +0 -869
  76. package/skills/flutter-senior-review/principles/dependencies-data-not-callbacks.md +0 -75
  77. package/skills/flutter-senior-review/principles/dependencies-provider-tree.md +0 -85
  78. package/skills/flutter-senior-review/principles/dependencies-temporal-coupling.md +0 -97
  79. package/skills/flutter-senior-review/principles/pragmatism-consistent-error-handling.md +0 -130
  80. package/skills/flutter-senior-review/principles/pragmatism-speculative-generality.md +0 -91
  81. package/skills/flutter-senior-review/principles/state-data-clumps.md +0 -64
  82. package/skills/flutter-senior-review/principles/state-invalid-states.md +0 -53
  83. package/skills/flutter-senior-review/principles/state-single-source-of-truth.md +0 -68
  84. package/skills/flutter-senior-review/principles/state-type-hierarchies.md +0 -75
  85. package/skills/flutter-senior-review/principles/structure-composition-over-config.md +0 -105
  86. package/skills/flutter-senior-review/principles/structure-shared-visual-patterns.md +0 -107
  87. package/skills/flutter-senior-review/principles/structure-wrapper-pattern.md +0 -90
@@ -1,75 +0,0 @@
1
- ---
2
- title: Reduce Coupling Through Data
3
- category: dependencies
4
- impact: MEDIUM-HIGH
5
- impactDescription: Stabilizes widget APIs
6
- tags: coupling, parameters, typed-objects, widget-api
7
- ---
8
-
9
- ## Reduce Coupling Through Data, Not Callbacks
10
-
11
- Pass typed objects, not callback parades. Child widgets receive a single typed object that encapsulates all the data they need.
12
-
13
- **Detection signals:**
14
- - Widgets have 4+ parameters beyond key and callbacks
15
- - Boolean flags being passed through multiple widget layers
16
- - Changing a child's behavior requires changing the parent's call site
17
- - Parameters that are only used in some conditions
18
-
19
- **Incorrect (flag parade):**
20
-
21
- ```dart
22
- QuestClaimButton(
23
- quest: quest,
24
- onSuccess: onClaimSuccess,
25
- isExpired: isExpired,
26
- isTutorial: isTutorial,
27
- bonus: bonus,
28
- showMultiplier: showMultiplier,
29
- isPremiumUser: isPremiumUser,
30
- )
31
- ```
32
-
33
- **Correct (typed data object):**
34
-
35
- ```dart
36
- // Data object encapsulates all context
37
- sealed class QuestClaimMode {
38
- final QuestEntity quest;
39
- final RewardBonus? bonus;
40
-
41
- const QuestClaimMode({required this.quest, this.bonus});
42
-
43
- factory QuestClaimMode.fromContext({
44
- required QuestEntity quest,
45
- required UserEntity? user,
46
- required bool isTutorialQuest,
47
- }) {
48
- // Decision logic centralized here
49
- if (quest.isExpired) return QuestClaimModeExpired(quest: quest);
50
- if (isTutorialQuest) return QuestClaimModeTutorial(quest: quest);
51
- return QuestClaimModeNormal(
52
- quest: quest,
53
- bonus: user?.applyRewardBonus(quest.reward),
54
- );
55
- }
56
- }
57
-
58
- // Clean widget API
59
- QuestClaimButton(
60
- mode: QuestClaimMode.fromContext(quest: quest, user: user, isTutorial: isTutorial),
61
- onSuccess: onClaimSuccess,
62
- )
63
- ```
64
-
65
- **Why it matters:**
66
- - Child widget's API is stable even as requirements change
67
- - Parent doesn't need to know child's internal decision logic
68
- - Data dependencies are explicit in the mode type
69
- - Easier to test: create mode objects directly
70
-
71
- **Detection questions:**
72
- - Do widgets have 4+ parameters beyond key and callbacks?
73
- - Are boolean flags being passed through multiple widget layers?
74
- - Does changing a child's behavior require changing the parent's call site?
75
- - Are there parameters that are only used in some conditions?
@@ -1,85 +0,0 @@
1
- ---
2
- title: Provider Tree Architecture
3
- category: dependencies
4
- impact: MEDIUM
5
- impactDescription: Clarifies data flow
6
- tags: riverpod, provider, architecture, dependencies
7
- ---
8
-
9
- ## Provider Tree Architecture
10
-
11
- Root -> branch -> leaf hierarchy for providers. Clear mental model of which providers depend on which.
12
-
13
- **Mental model:**
14
- - **Root providers**: Match app lifecycle, never disposed. Hold core entities (user, games, quests). Referenced from many places. Use `keepAlive: true`.
15
- - **Branch providers**: Combine/filter/transform core data. May become "core" as app grows (e.g., `squadQuestsProvider`).
16
- - **Leaf providers**: Screen-specific or ephemeral. Depend on branches, rarely watched by other providers.
17
-
18
- **Detection signals:**
19
- - Can't draw the provider dependency graph as a tree
20
- - Circular or confusing dependency chains
21
- - "Leaf" providers being watched by other providers
22
- - Provider doing too much (should split into root + branch)
23
-
24
- **Incorrect (flat, tangled):**
25
-
26
- ```dart
27
- // Hard to trace what depends on what
28
- final userProvider = ...;
29
- final questsProvider = ...; // watches userProvider
30
- final gamesProvider = ...; // watches userProvider
31
- final filteredQuestsProvider = ...; // watches questsProvider, gamesProvider, some other provider
32
- final screenStateProvider = ...; // watches 5 different providers
33
- ```
34
-
35
- **Correct (tree structure):**
36
-
37
- ```dart
38
- // Root (core entities, keepAlive: true)
39
- @Riverpod(keepAlive: true)
40
- Future<User> user(Ref ref) => ref.watch(authProvider.future).then((auth) => auth.user);
41
-
42
- @Riverpod(keepAlive: true)
43
- Future<List<Quest>> quests(Ref ref) async {
44
- final user = await ref.watch(userProvider.future);
45
- if (user == null) return [];
46
- return ref.watch(questsApiProvider).fetchQuests(user.id);
47
- }
48
-
49
- // Branch (derived/filtered, may become core as app grows)
50
- @riverpod
51
- Future<List<Quest>> squadQuests(Ref ref) async {
52
- final quests = await ref.watch(questsProvider.future);
53
- return quests.where((q) => q.type == QuestType.squad).toList();
54
- }
55
-
56
- @riverpod
57
- Future<List<Quest>> soloQuests(Ref ref) async {
58
- final quests = await ref.watch(questsProvider.future);
59
- return quests.where((q) => q.type == QuestType.solo).toList();
60
- }
61
-
62
- // Leaf (screen-specific, ephemeral)
63
- @riverpod
64
- class SquadQuestScreenState extends _$SquadQuestScreenState {
65
- @override
66
- FutureOr<ScreenState> build() async {
67
- // Only watches branch providers
68
- final quests = await ref.watch(squadQuestsProvider.future);
69
- return ScreenState(quests: quests);
70
- }
71
- // Never watched by other providers
72
- }
73
- ```
74
-
75
- **Why it matters:**
76
- - Clear mental model of data flow
77
- - Predictable rebuild scope
78
- - Easier to add new derived providers
79
- - Natural place for each piece of logic
80
-
81
- **Detection questions:**
82
- - Can you draw the provider dependency graph as a tree?
83
- - Are there circular or confusing dependency chains?
84
- - Are "leaf" providers being watched by other providers?
85
- - Is a provider doing too much (should it split into root + branch)?
@@ -1,97 +0,0 @@
1
- ---
2
- title: Temporal Coupling
3
- category: dependencies
4
- impact: MEDIUM
5
- impactDescription: Catches misuse at compile time
6
- tags: builder-pattern, type-state, initialization, sequence
7
- ---
8
-
9
- ## Temporal Coupling (Enforce Sequences)
10
-
11
- Enforce operation sequences via types, not documentation. Make it impossible to call methods in the wrong order.
12
-
13
- **Detection signals:**
14
- - Methods that must be called before others
15
- - Comments like "must call X first" or "call after Y"
16
- - Objects can be in an "invalid" state between operations
17
- - Tests have setup steps that could be forgotten
18
-
19
- **Incorrect (implicit sequence):**
20
-
21
- ```dart
22
- class PaymentProcessor {
23
- void init() { ... }
24
- void setAmount(int amount) { ... }
25
- void setCustomer(Customer c) { ... }
26
- Future<void> process() { ... } // Must call init, setAmount, setCustomer first!
27
- }
28
-
29
- // Easy to misuse:
30
- final processor = PaymentProcessor();
31
- processor.process(); // Boom - forgot to init
32
- ```
33
-
34
- **Correct (builder pattern):**
35
-
36
- ```dart
37
- class PaymentBuilder {
38
- int? _amount;
39
- Customer? _customer;
40
-
41
- PaymentBuilder withAmount(int amount) {
42
- _amount = amount;
43
- return this;
44
- }
45
-
46
- PaymentBuilder withCustomer(Customer c) {
47
- _customer = c;
48
- return this;
49
- }
50
-
51
- Payment build() {
52
- assert(_amount != null && _customer != null);
53
- return Payment(amount: _amount!, customer: _customer!);
54
- }
55
- }
56
-
57
- // Usage is clear
58
- final payment = PaymentBuilder()
59
- .withAmount(100)
60
- .withCustomer(customer)
61
- .build();
62
- ```
63
-
64
- **Correct (type-state pattern):**
65
-
66
- ```dart
67
- // Types enforce valid sequences
68
- class UninitializedProcessor {
69
- InitializedProcessor init(Config config) => InitializedProcessor(config);
70
- }
71
-
72
- class InitializedProcessor {
73
- final Config _config;
74
- InitializedProcessor(this._config);
75
-
76
- Future<Result> process(int amount, Customer c) async {
77
- // Can only be called on initialized processor
78
- return _processPayment(amount, c);
79
- }
80
- }
81
-
82
- // Misuse is a compile error
83
- final processor = UninitializedProcessor();
84
- processor.process(100, customer); // Error: process is not defined on UninitializedProcessor
85
- ```
86
-
87
- **Why it matters:**
88
- - Compiler catches misuse, not runtime
89
- - Self-documenting: types show valid sequences
90
- - Impossible to forget required steps
91
- - Easier onboarding for new developers
92
-
93
- **Detection questions:**
94
- - Are there methods that must be called before others?
95
- - Are there comments like "must call X first" or "call after Y"?
96
- - Can objects be in an "invalid" state between operations?
97
- - Do tests have setup steps that could be forgotten?
@@ -1,130 +0,0 @@
1
- ---
2
- title: Consistent Error Handling
3
- category: pragmatism
4
- impact: MEDIUM
5
- impactDescription: Improves UX and debugging
6
- tags: error-handling, async-value, riverpod, toast
7
- ---
8
-
9
- ## Consistent Error Handling Strategy
10
-
11
- One strategy applied everywhere, not ad-hoc try/catch. Use AsyncValue for state management and centralized error presentation.
12
-
13
- **Detection signals:**
14
- - Errors appear differently across screens (toasts vs dialogs vs inline)
15
- - try/catch blocks scattered in widget code
16
- - Inconsistent error messaging
17
- - No standard retry mechanism
18
-
19
- **Incorrect (ad-hoc handling):**
20
-
21
- ```dart
22
- // Screen 1: Try/catch with toast
23
- try {
24
- await ref.read(saveProvider.notifier).save();
25
- } catch (e) {
26
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('$e')));
27
- }
28
-
29
- // Screen 2: Different pattern
30
- final result = await ref.read(saveProvider.notifier).save();
31
- if (result.isError) {
32
- showDialog(...); // Different error UI
33
- }
34
-
35
- // Screen 3: No error handling at all
36
- await ref.read(saveProvider.notifier).save(); // Hope it works!
37
- ```
38
-
39
- **Correct (consistent pattern):**
40
-
41
- ```dart
42
- // Providers handle errors uniformly with AsyncValue.guard
43
- @riverpod
44
- class SaveController extends _$SaveController {
45
- @override
46
- FutureOr<void> build() => null;
47
-
48
- Future<void> save(Data data) async {
49
- state = const AsyncLoading();
50
- state = await AsyncValue.guard(() => _repository.save(data));
51
- // Error stays in state, not thrown
52
- // Success navigates or shows confirmation
53
- if (state.hasValue) {
54
- ref.invalidate(dataProvider);
55
- }
56
- }
57
- }
58
-
59
- // Extension for centralized error listening
60
- extension WidgetRefEx on WidgetRef {
61
- void listenOnError<T>(
62
- ProviderListenable<AsyncValue<T>> provider, {
63
- bool Function(Object error)? ignoreIf,
64
- }) {
65
- listen(provider, (_, next) {
66
- next.whenOrNull(
67
- error: (error, _) {
68
- if (ignoreIf?.call(error) == true) return;
69
- AppToast.showError(context, error.toString());
70
- },
71
- );
72
- });
73
- }
74
- }
75
-
76
- // Screens use consistent pattern
77
- Widget build(context, ref) {
78
- // Centralized error listening - shows toast on any error
79
- ref.listenOnError(saveProvider);
80
-
81
- final saveState = ref.watch(saveProvider);
82
-
83
- return AppPrimaryButton(
84
- isLoading: saveState.isLoading,
85
- onPressed: () => ref.read(saveProvider.notifier).save(data),
86
- child: Text('Save'),
87
- );
88
- }
89
- ```
90
-
91
- **For first-load errors (need retry UI):**
92
-
93
- ```dart
94
- Widget build(context, ref) {
95
- final dataState = ref.watch(dataProvider);
96
-
97
- return dataState.when(
98
- data: (data) => DataContent(data: data),
99
- loading: () => const AppLoadingIndicator(),
100
- error: (error, _) => Center(
101
- child: Column(
102
- mainAxisAlignment: MainAxisAlignment.center,
103
- children: [
104
- Icon(Icons.error_outline, size: 64, color: context.colorScheme.error),
105
- const SizedBox(height: 16),
106
- Text(LocaleKeys.common_error.tr()),
107
- const SizedBox(height: 16),
108
- FilledButton.icon(
109
- onPressed: () => ref.invalidate(dataProvider),
110
- icon: const Icon(Icons.refresh),
111
- label: Text(LocaleKeys.common_retry.tr()),
112
- ),
113
- ],
114
- ),
115
- ),
116
- );
117
- }
118
- ```
119
-
120
- **Why it matters:**
121
- - Users get consistent experience
122
- - Developers follow one pattern
123
- - Error states are explicit and testable
124
- - Retry logic is standardized
125
-
126
- **Detection questions:**
127
- - Do errors appear the same way across all screens?
128
- - Are there try/catch blocks in widget code?
129
- - Is error messaging consistent (toasts vs dialogs vs inline)?
130
- - Is there a standard retry mechanism?
@@ -1,91 +0,0 @@
1
- ---
2
- title: Speculative Generality
3
- category: pragmatism
4
- impact: MEDIUM
5
- impactDescription: Reduces unnecessary complexity
6
- tags: abstraction, yagni, over-engineering, interfaces
7
- ---
8
-
9
- ## Speculative Generality (Know When NOT to Abstract)
10
-
11
- Don't abstract until 2-3 concrete cases exist. Build for today's requirements; extract abstractions when you have real examples.
12
-
13
- **Detection signals:**
14
- - Interface with only one implementation
15
- - Factory that only creates one type
16
- - Configuration options no one uses
17
- - Abstraction added "in case we need it later"
18
-
19
- **Incorrect (premature abstraction):**
20
-
21
- ```dart
22
- // "We might need different storage backends someday"
23
- abstract class StorageStrategy {
24
- Future<void> save(String key, String value);
25
- Future<String?> load(String key);
26
- }
27
-
28
- class LocalStorageStrategy implements StorageStrategy {
29
- @override
30
- Future<void> save(String key, String value) => _prefs.setString(key, value);
31
-
32
- @override
33
- Future<String?> load(String key) => _prefs.getString(key);
34
- }
35
-
36
- class StorageFactory {
37
- static StorageStrategy create(StorageType type) => switch (type) {
38
- StorageType.local => LocalStorageStrategy(), // Only one ever used
39
- };
40
- }
41
-
42
- // "We might need to support multiple payment providers"
43
- abstract class PaymentProvider { ... }
44
- class StripeProvider implements PaymentProvider { ... } // Only one exists
45
- ```
46
-
47
- **Correct (direct usage):**
48
-
49
- ```dart
50
- // Just use the thing directly
51
- class LocalStorage {
52
- final SharedPreferences _prefs;
53
-
54
- LocalStorage(this._prefs);
55
-
56
- Future<void> save(String key, String value) => _prefs.setString(key, value);
57
- Future<String?> load(String key) async => _prefs.getString(key);
58
- }
59
-
60
- // When you ACTUALLY need a second implementation, THEN abstract
61
- // The refactoring is straightforward and you'll know the right abstraction
62
- // because you have concrete examples of the variation
63
- ```
64
-
65
- **When TO abstract:**
66
-
67
- ```dart
68
- // You have 2+ real implementations with actual differences
69
- abstract class AuthProvider {
70
- Future<User> signIn();
71
- Future<void> signOut();
72
- }
73
-
74
- class GoogleAuthProvider implements AuthProvider { ... }
75
- class AppleAuthProvider implements AuthProvider { ... }
76
- class EmailAuthProvider implements AuthProvider { ... }
77
-
78
- // The abstraction is earned - you know exactly what varies
79
- ```
80
-
81
- **Why it matters:**
82
- - Less code to maintain
83
- - Abstractions based on real needs fit better
84
- - Easier to understand: no indirection to trace
85
- - YAGNI: You Aren't Gonna Need It
86
-
87
- **Detection questions:**
88
- - Is there an interface with only one implementation?
89
- - Is there a factory that only creates one type?
90
- - Are there configuration options no one uses?
91
- - Was this abstraction added "in case we need it later"?
@@ -1,64 +0,0 @@
1
- ---
2
- title: Data Clumps to Records
3
- category: state
4
- impact: MEDIUM-HIGH
5
- impactDescription: Reduces parameter proliferation
6
- tags: record, typedef, data-modeling, parameters
7
- ---
8
-
9
- ## Data Clumps to Records
10
-
11
- Group parameters that travel together into typed objects (records or classes).
12
-
13
- **Detection signals:**
14
- - Same 3+ parameters appear in multiple function signatures
15
- - Parameters are logically related (always used together)
16
- - Adding a new related field requires updating many signatures
17
- - Bugs from passing parameters in the wrong order
18
-
19
- **Incorrect (repeated parameter groups):**
20
-
21
- ```dart
22
- void showReward(int baseReward, int? boostedReward, int? multiplier) { ... }
23
- void displayBadge(int baseReward, int? boostedReward, int? multiplier) { ... }
24
- void logClaim(int baseReward, int? boostedReward, int? multiplier) { ... }
25
-
26
- Widget buildReward({
27
- required int baseReward,
28
- int? boostedReward,
29
- int? multiplier,
30
- }) { ... }
31
- ```
32
-
33
- **Correct (typed record/class):**
34
-
35
- ```dart
36
- // Record for simple data grouping
37
- typedef RewardBonus = ({int base, int? boosted, int? multiplier});
38
-
39
- // Or class if behavior is needed
40
- class RewardCalculation {
41
- final int base;
42
- final int? boosted;
43
- final int? multiplier;
44
-
45
- bool get hasBonus => multiplier != null;
46
- int get displayAmount => boosted ?? base;
47
- }
48
-
49
- // Clean call sites
50
- void showReward(RewardBonus bonus) { ... }
51
- Widget buildReward({required RewardBonus bonus}) { ... }
52
- ```
53
-
54
- **Why it matters:**
55
- - Single place to add new related fields
56
- - Impossible to pass parameters in wrong order
57
- - Semantic meaning is clear
58
- - Reduces parameter count everywhere
59
-
60
- **Detection questions:**
61
- - Do the same 3+ parameters appear in multiple function signatures?
62
- - Are parameters logically related (always used together)?
63
- - Would adding a new related field require updating many signatures?
64
- - Are there bugs from passing parameters in the wrong order?
@@ -1,53 +0,0 @@
1
- ---
2
- title: Make Invalid States Unrepresentable
3
- category: state
4
- impact: CRITICAL
5
- impactDescription: Eliminates entire class of bugs
6
- tags: sealed-class, boolean-flags, state-modeling, type-safety
7
- ---
8
-
9
- ## Make Invalid States Unrepresentable
10
-
11
- Replace boolean flag combinations with sealed class hierarchies where each variant represents exactly one valid state.
12
-
13
- **Detection signals:**
14
- - 3+ boolean parameters passed together
15
- - Same boolean checks repeated in multiple places
16
- - if/else chains checking flag combinations
17
- - Some flag combinations would cause undefined behavior
18
-
19
- **Incorrect (boolean flag explosion):**
20
-
21
- ```dart
22
- Widget build() {
23
- final isLoading = ...;
24
- final isExpired = ...;
25
- final isTutorial = ...;
26
- final hasBonus = ...;
27
- // What happens when isTutorial && isExpired?
28
- // What about isLoading && hasBonus && isTutorial?
29
- }
30
- ```
31
-
32
- **Correct (sealed class hierarchy):**
33
-
34
- ```dart
35
- sealed class ItemMode {
36
- const ItemMode();
37
- }
38
- final class ItemModeNormal extends ItemMode { ... }
39
- final class ItemModeTutorial extends ItemMode { ... }
40
- final class ItemModeExpired extends ItemMode { ... }
41
- ```
42
-
43
- **Why it matters:**
44
- - Compiler enforces exhaustive handling via switch expressions
45
- - New states added explicitly, not as boolean combinations
46
- - Impossible to create invalid state combinations
47
- - Self-documenting: sealed class shows all possible states
48
-
49
- **Detection questions:**
50
- - Are there 3+ boolean parameters being passed together?
51
- - Do you see the same boolean checks repeated in multiple places?
52
- - Are there if/else chains checking combinations of flags?
53
- - Could some flag combinations cause undefined behavior?
@@ -1,68 +0,0 @@
1
- ---
2
- title: Single Source of Truth
3
- category: state
4
- impact: HIGH
5
- impactDescription: Prevents stale data bugs
6
- tags: state-ownership, provider, derived-state, riverpod
7
- ---
8
-
9
- ## Single Source of Truth (State Ownership)
10
-
11
- One owner per state, derive the rest via selectors. Push long-lived state up to providers; keep only ephemeral UI state local.
12
-
13
- **Detection signals:**
14
- - Same data stored in both a provider and local widget state
15
- - useEffect hooks syncing local state from provider state
16
- - Two sources of truth could disagree
17
- - Derived data being cached instead of computed
18
-
19
- **Incorrect (duplicated state):**
20
-
21
- ```dart
22
- class ItemScreen extends HookConsumerWidget {
23
- Widget build(context, ref) {
24
- final items = ref.watch(itemsProvider);
25
- // Local state duplicating what provider knows
26
- final selectedItem = useState<Item?>(null);
27
- final isEditing = useState(false);
28
-
29
- // Widget caches a derived value
30
- final totalPrice = useState(0);
31
- useEffect(() {
32
- totalPrice.value = items.fold(0, (sum, i) => sum + i.price);
33
- }, [items]);
34
- }
35
- }
36
- ```
37
-
38
- **Correct (single owner, derived values):**
39
-
40
- ```dart
41
- // Provider owns the state
42
- @riverpod
43
- class ItemsController extends _$ItemsController {
44
- Item? get selectedItem => state.value?.firstWhereOrNull((i) => i.isSelected);
45
- int get totalPrice => state.value?.fold(0, (sum, i) => sum + i.price) ?? 0;
46
- }
47
-
48
- // Widget only has ephemeral UI state
49
- class ItemScreen extends HookConsumerWidget {
50
- Widget build(context, ref) {
51
- final items = ref.watch(itemsProvider);
52
- final selectedItem = ref.watch(itemsProvider.select((s) => s.selectedItem));
53
- final isTextFieldFocused = useState(false); // Truly ephemeral
54
- }
55
- }
56
- ```
57
-
58
- **Why it matters:**
59
- - No "which value is authoritative?" confusion
60
- - State updates propagate automatically
61
- - Easier debugging: one place to inspect
62
- - Prevents stale data bugs
63
-
64
- **Detection questions:**
65
- - Is the same data stored in both a provider and local widget state?
66
- - Are there useEffect hooks syncing local state from provider state?
67
- - Could two sources of truth disagree? What happens then?
68
- - Is there derived data being cached instead of computed?