mindsystem-cc 3.3.2 → 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,64 @@
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?
@@ -0,0 +1,53 @@
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?
@@ -0,0 +1,68 @@
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?
@@ -0,0 +1,75 @@
1
+ ---
2
+ title: Explicit Type Hierarchies Over Implicit Conventions
3
+ category: state
4
+ impact: HIGH
5
+ impactDescription: Centralizes decision logic
6
+ tags: factory, sealed-class, decision-logic, type-safety
7
+ ---
8
+
9
+ ## Explicit Type Hierarchies Over Implicit Conventions
10
+
11
+ Use factories to encapsulate decision logic. Widgets receive pre-computed decisions, not raw data to interpret.
12
+
13
+ **Detection signals:**
14
+ - Complex if/else chains determining which widget to render
15
+ - Same decision logic duplicated across multiple widgets
16
+ - Widgets receive data they only use to make decisions (not to display)
17
+ - New requirement would add another boolean parameter
18
+
19
+ **Incorrect (scattered decision logic):**
20
+
21
+ ```dart
22
+ Widget _buildTrailing(BuildContext context, {required bool isExpired, required bool isTutorial}) {
23
+ if (quest.status.isClaimable && quest.isExpired) {
24
+ return QuestClaimButton(quest: quest, isExpired: true);
25
+ }
26
+ if (quest.canClaim) {
27
+ if (isTutorial) {
28
+ return PulsingGlowWrapper(child: QuestClaimButton(quest: quest, isTutorial: true));
29
+ }
30
+ return QuestClaimButton(quest: quest);
31
+ }
32
+ return _buildRewardBadge(context);
33
+ }
34
+ ```
35
+
36
+ **Correct (factory with type hierarchy):**
37
+
38
+ ```dart
39
+ sealed class QuestClaimMode {
40
+ factory QuestClaimMode.fromContext({
41
+ required QuestEntity quest,
42
+ required UserEntity? user,
43
+ required bool isTutorialQuest,
44
+ }) {
45
+ if (quest.status.isClaimable && quest.isExpired) {
46
+ return QuestClaimModeExpired(...);
47
+ }
48
+ if (quest.canClaim) {
49
+ return isTutorialQuest
50
+ ? QuestClaimModeTutorial(...)
51
+ : QuestClaimModeNormal(...);
52
+ }
53
+ return QuestClaimModePending(...);
54
+ }
55
+ }
56
+
57
+ // Widget becomes a simple switch
58
+ Widget _buildTrailing() => switch (claimMode) {
59
+ QuestClaimModePending() => QuestRewardDisplay(...),
60
+ QuestClaimModeTutorial() => _wrapWithTutorialEffects(QuestClaimButton(mode: claimMode)),
61
+ QuestClaimModeNormal() || QuestClaimModeExpired() => QuestClaimButton(mode: claimMode),
62
+ };
63
+ ```
64
+
65
+ **Why it matters:**
66
+ - Decision logic lives in one place (the factory)
67
+ - Widgets receive pre-computed decisions, not raw data to interpret
68
+ - Adding new modes is explicit and compiler-checked
69
+ - Testing is clearer: test the factory, then test each mode's rendering
70
+
71
+ **Detection questions:**
72
+ - Are there complex if/else chains determining which widget to render?
73
+ - Is the same decision logic duplicated across multiple widgets?
74
+ - Do widgets receive data they only use to make decisions (not to display)?
75
+ - Would a new requirement add another boolean parameter?
@@ -0,0 +1,105 @@
1
+ ---
2
+ title: Composition Over Configuration
3
+ category: structure
4
+ impact: MEDIUM
5
+ impactDescription: Simplifies widget APIs
6
+ tags: composition, widget-api, god-widget, parameters
7
+ ---
8
+
9
+ ## Composition Over Configuration
10
+
11
+ Small focused widgets over god widgets with many flags. Use builders and slots instead of boolean parameters.
12
+
13
+ **Detection signals:**
14
+ - Widget has more than 6-8 parameters
15
+ - Boolean parameters that are mutually exclusive
16
+ - Widget is really 3 different widgets pretending to be 1
17
+ - Adding a new variant would require another boolean flag
18
+
19
+ **Incorrect (god widget):**
20
+
21
+ ```dart
22
+ AppButton(
23
+ label: 'Submit',
24
+ isPrimary: true,
25
+ isSecondary: false,
26
+ isDestructive: false,
27
+ isLoading: false,
28
+ isDisabled: false,
29
+ showIcon: true,
30
+ iconPosition: IconPosition.left,
31
+ size: ButtonSize.medium,
32
+ // ... 10 more parameters
33
+ )
34
+ ```
35
+
36
+ **Correct (composed widgets):**
37
+
38
+ ```dart
39
+ // Separate widgets for distinct purposes
40
+ class PrimaryButton extends StatelessWidget {
41
+ final VoidCallback? onPressed;
42
+ final Widget child;
43
+ final bool isLoading;
44
+
45
+ const PrimaryButton({
46
+ super.key,
47
+ required this.onPressed,
48
+ required this.child,
49
+ this.isLoading = false,
50
+ });
51
+
52
+ // ...
53
+ }
54
+
55
+ // Composition for custom layouts
56
+ PrimaryButton(
57
+ onPressed: handleSubmit,
58
+ child: Row(
59
+ mainAxisSize: MainAxisSize.min,
60
+ children: [
61
+ Icon(Icons.check),
62
+ SizedBox(width: 8),
63
+ Text('Submit'),
64
+ ],
65
+ ),
66
+ )
67
+
68
+ // Or slot-based API for common patterns
69
+ PrimaryButton.icon(
70
+ icon: Icons.check,
71
+ label: 'Submit',
72
+ onPressed: handleSubmit,
73
+ )
74
+ ```
75
+
76
+ **Alternative: Use sealed class for mutually exclusive variants:**
77
+
78
+ ```dart
79
+ sealed class ButtonVariant {
80
+ const ButtonVariant();
81
+ }
82
+ class PrimaryVariant extends ButtonVariant { ... }
83
+ class SecondaryVariant extends ButtonVariant { ... }
84
+ class DestructiveVariant extends ButtonVariant { ... }
85
+
86
+ class AppButton extends StatelessWidget {
87
+ final ButtonVariant variant;
88
+ final Widget child;
89
+ final VoidCallback? onPressed;
90
+
91
+ // Single widget, but variants are explicit and exhaustive
92
+ }
93
+ ```
94
+
95
+ **Why it matters:**
96
+ - Each widget has one job
97
+ - New variants don't bloat existing widgets
98
+ - Easier to understand and test
99
+ - Flexible: compose for custom needs, use shortcuts for common ones
100
+
101
+ **Detection questions:**
102
+ - Does this widget have more than 6-8 parameters?
103
+ - Are there boolean parameters that are mutually exclusive?
104
+ - Is this widget really 3 different widgets pretending to be 1?
105
+ - Would adding a new variant require another boolean flag?
@@ -0,0 +1,107 @@
1
+ ---
2
+ title: Extract Shared Visual Patterns
3
+ category: structure
4
+ impact: MEDIUM-HIGH
5
+ impactDescription: Guarantees visual consistency
6
+ tags: widget-extraction, style-variants, ui-deduplication, decoration
7
+ ---
8
+
9
+ ## Extract Shared Visual Patterns
10
+
11
+ Deduplicate UI with style variants. When similar visual patterns appear 2+ times, extract a shared widget with an enum or sealed class for variants.
12
+
13
+ **Detection signals:**
14
+ - Similar Container/decoration patterns across multiple widgets
15
+ - Visual elements (colors, padding, borders) vary based on state
16
+ - Design change would require updating multiple files
17
+ - Subtle inconsistencies between similar UI elements
18
+
19
+ **Incorrect (duplicated patterns):**
20
+
21
+ ```dart
22
+ // In QuestClaimButton
23
+ Container(
24
+ padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
25
+ decoration: BoxDecoration(
26
+ color: isExpired ? AppColors.gray400 : AppColors.forge,
27
+ borderRadius: BorderRadius.circular(20),
28
+ ),
29
+ child: Row(children: [Text('grape'), Text('$reward')]),
30
+ )
31
+
32
+ // In QuestListTile (slightly different)
33
+ Container(
34
+ padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
35
+ decoration: BoxDecoration(
36
+ borderRadius: BorderRadius.circular(20),
37
+ border: Border.all(color: ...),
38
+ ),
39
+ child: Row(children: [Text('grape'), Text('$reward')]),
40
+ )
41
+ ```
42
+
43
+ **Correct (shared widget with variants):**
44
+
45
+ ```dart
46
+ enum QuestRewardStyle { filled, outlined, disabled }
47
+
48
+ class QuestRewardDisplay extends StatelessWidget {
49
+ final int reward;
50
+ final QuestRewardStyle style;
51
+
52
+ const QuestRewardDisplay({
53
+ super.key,
54
+ required this.reward,
55
+ this.style = QuestRewardStyle.filled,
56
+ });
57
+
58
+ Widget build(context) {
59
+ return Container(
60
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
61
+ decoration: style.buildDecoration(context),
62
+ child: Row(
63
+ mainAxisSize: MainAxisSize.min,
64
+ children: [
65
+ Text('grape'),
66
+ const SizedBox(width: 4),
67
+ Text('$reward', style: style.buildTextStyle(context)),
68
+ ],
69
+ ),
70
+ );
71
+ }
72
+ }
73
+
74
+ extension on QuestRewardStyle {
75
+ BoxDecoration buildDecoration(BuildContext context) => switch (this) {
76
+ QuestRewardStyle.filled => BoxDecoration(
77
+ color: AppColors.forge,
78
+ borderRadius: BorderRadius.circular(20),
79
+ ),
80
+ QuestRewardStyle.outlined => BoxDecoration(
81
+ borderRadius: BorderRadius.circular(20),
82
+ border: Border.all(color: AppColors.forge),
83
+ ),
84
+ QuestRewardStyle.disabled => BoxDecoration(
85
+ color: AppColors.gray400,
86
+ borderRadius: BorderRadius.circular(20),
87
+ ),
88
+ };
89
+
90
+ TextStyle buildTextStyle(BuildContext context) => switch (this) {
91
+ QuestRewardStyle.disabled => context.textTheme.bodyMedium!.copyWith(color: AppColors.gray600),
92
+ _ => context.textTheme.bodyMedium!,
93
+ };
94
+ }
95
+ ```
96
+
97
+ **Why it matters:**
98
+ - Visual consistency is guaranteed
99
+ - Style changes propagate automatically
100
+ - New variants are added in one place
101
+ - Reduces widget file sizes significantly
102
+
103
+ **Detection questions:**
104
+ - Are there similar Container/decoration patterns across multiple widgets?
105
+ - Do visual elements (colors, padding, borders) vary based on state?
106
+ - Would a design change require updating multiple files?
107
+ - Are there subtle inconsistencies between similar UI elements?
@@ -0,0 +1,90 @@
1
+ ---
2
+ title: Isolate Feature Responsibility (Wrapper Pattern)
3
+ category: structure
4
+ impact: HIGH
5
+ impactDescription: Features become removable
6
+ tags: wrapper, composition, feature-isolation, widget-extraction
7
+ ---
8
+
9
+ ## Isolate Feature Responsibility (Wrapper Pattern)
10
+
11
+ Extract optional feature logic into wrapper components. The wrapper owns all feature-specific state; the core component doesn't know the feature exists.
12
+
13
+ **Detection signals:**
14
+ - More than 30% of a widget's code dedicated to one optional feature
15
+ - Removing a feature requires deleting scattered lines throughout the file
16
+ - Multiple `if (featureEnabled)` checks spread across the widget
17
+ - State variables only used by one feature
18
+
19
+ **Incorrect (scattered feature logic):**
20
+
21
+ ```dart
22
+ class QuestListScreen extends HookConsumerWidget {
23
+ Widget build(context, ref) {
24
+ // 50 lines of tutorial-specific state management
25
+ final tutorialKey = useMemoized(GlobalKey.new);
26
+ final overlayVisible = useState(true);
27
+ final cutoutRect = useState<Rect?>(null);
28
+
29
+ // Tutorial measurement logic mixed with list logic
30
+ useEffect(() {
31
+ if (isTutorialActive) {
32
+ // 30 lines of tutorial position measurement
33
+ }
34
+ });
35
+
36
+ // Main list rendering polluted with tutorial checks
37
+ return Stack(
38
+ children: [
39
+ questList,
40
+ if (showTutorialOverlay) TutorialOverlay(...),
41
+ ],
42
+ );
43
+ }
44
+ }
45
+ ```
46
+
47
+ **Correct (wrapper pattern):**
48
+
49
+ ```dart
50
+ // Tutorial logic fully encapsulated
51
+ class TutorialQuestSpotlight extends HookConsumerWidget {
52
+ final Widget Function(BuildContext, {GlobalKey? tutorialKey}) builder;
53
+
54
+ Widget build(context, ref) {
55
+ // All tutorial state lives here
56
+ final tutorialKey = useMemoized(GlobalKey.new);
57
+ final overlayVisible = useState(true);
58
+ final cutoutRect = useState<Rect?>(null);
59
+
60
+ // Core list doesn't know tutorials exist
61
+ return Stack(
62
+ children: [
63
+ builder(context, tutorialKey: tutorialKey),
64
+ if (overlayVisible.value) TutorialOverlay(cutoutRect: cutoutRect.value),
65
+ ],
66
+ );
67
+ }
68
+ }
69
+
70
+ // Clean core component
71
+ class QuestListScreen extends HookConsumerWidget {
72
+ Widget build(context, ref) {
73
+ return TutorialQuestSpotlight(
74
+ builder: (context, {tutorialKey}) => _buildQuestList(tutorialKey),
75
+ );
76
+ }
77
+ }
78
+ ```
79
+
80
+ **Why it matters:**
81
+ - Feature can be disabled/removed by removing one wrapper
82
+ - Core component remains focused and testable
83
+ - Feature logic is cohesive and isolated
84
+ - Multiple features can compose without polluting each other
85
+
86
+ **Detection questions:**
87
+ - Is more than 30% of a widget's code dedicated to one optional feature?
88
+ - Would removing a feature require deleting scattered lines throughout the file?
89
+ - Are there multiple `if (featureEnabled)` checks spread across the widget?
90
+ - Does the widget have state variables only used by one feature?