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.
- package/README.md +4 -12
- package/agents/ms-debugger.md +196 -880
- package/agents/ms-plan-checker.md +30 -30
- package/agents/ms-plan-writer.md +1 -1
- package/agents/ms-product-researcher.md +4 -2
- package/agents/ms-verifier.md +25 -117
- package/commands/ms/add-phase.md +3 -4
- package/commands/ms/add-todo.md +3 -4
- package/commands/ms/adhoc.md +3 -4
- package/commands/ms/audit-milestone.md +4 -3
- package/commands/ms/complete-milestone.md +2 -2
- package/commands/ms/config.md +36 -9
- package/commands/ms/create-roadmap.md +3 -4
- package/commands/ms/debug.md +27 -28
- package/commands/ms/design-phase.md +8 -5
- package/commands/ms/discuss-phase.md +2 -2
- package/commands/ms/doctor.md +9 -6
- package/commands/ms/execute-phase.md +2 -5
- package/commands/ms/help.md +2 -2
- package/commands/ms/insert-phase.md +3 -4
- package/commands/ms/map-codebase.md +1 -2
- package/commands/ms/new-milestone.md +1 -3
- package/commands/ms/new-project.md +3 -5
- package/commands/ms/plan-milestone-gaps.md +3 -4
- package/commands/ms/plan-phase.md +2 -3
- package/commands/ms/progress.md +1 -0
- package/commands/ms/remove-phase.md +3 -4
- package/commands/ms/research-phase.md +4 -4
- package/commands/ms/research-project.md +9 -16
- package/commands/ms/review-design.md +4 -2
- package/commands/ms/verify-work.md +6 -8
- package/mindsystem/templates/config.json +2 -1
- package/mindsystem/templates/roadmap.md +1 -1
- package/mindsystem/templates/state.md +2 -2
- package/mindsystem/templates/verification-report.md +3 -26
- package/mindsystem/workflows/diagnose-issues.md +0 -1
- package/mindsystem/workflows/discuss-phase.md +7 -3
- package/mindsystem/workflows/execute-phase.md +2 -18
- package/mindsystem/workflows/map-codebase.md +6 -12
- package/mindsystem/workflows/mockup-generation.md +46 -22
- package/mindsystem/workflows/plan-phase.md +12 -5
- package/mindsystem/workflows/verify-work.md +96 -69
- package/package.json +1 -1
- package/scripts/__pycache__/ms-tools.cpython-314.pyc +0 -0
- package/scripts/__pycache__/test_ms_tools.cpython-314-pytest-9.0.2.pyc +0 -0
- package/scripts/ms-tools.py +751 -6
- package/scripts/test_ms_tools.py +786 -0
- package/skills/senior-review/AGENTS.md +531 -0
- package/skills/{flutter-senior-review → senior-review}/SKILL.md +47 -36
- package/skills/senior-review/principles/dependencies-api-boundary-design.md +32 -0
- package/skills/senior-review/principles/dependencies-data-not-flags.md +32 -0
- package/skills/senior-review/principles/dependencies-temporal-coupling.md +32 -0
- package/skills/senior-review/principles/pragmatism-consistent-error-handling.md +32 -0
- package/skills/senior-review/principles/pragmatism-speculative-generality.md +32 -0
- package/skills/senior-review/principles/state-invalid-states.md +33 -0
- package/skills/senior-review/principles/state-single-source-of-truth.md +32 -0
- package/skills/senior-review/principles/state-type-hierarchies.md +32 -0
- package/skills/senior-review/principles/structure-composition-over-config.md +32 -0
- package/skills/senior-review/principles/structure-feature-isolation.md +32 -0
- package/skills/senior-review/principles/structure-module-cohesion.md +32 -0
- package/agents/ms-flutter-code-quality.md +0 -169
- package/agents/ms-flutter-reviewer.md +0 -211
- package/agents/ms-flutter-simplifier.md +0 -79
- package/mindsystem/references/debugging/debugging-mindset.md +0 -11
- package/mindsystem/references/debugging/hypothesis-testing.md +0 -11
- package/mindsystem/references/debugging/investigation-techniques.md +0 -11
- package/mindsystem/references/debugging/verification-patterns.md +0 -11
- package/mindsystem/references/debugging/when-to-research.md +0 -11
- package/mindsystem/references/git-integration.md +0 -254
- package/mindsystem/references/verification-patterns.md +0 -595
- package/mindsystem/workflows/debug.md +0 -14
- package/mindsystem/workflows/verify-phase.md +0 -625
- package/skills/flutter-code-quality/SKILL.md +0 -143
- package/skills/flutter-code-simplification/SKILL.md +0 -102
- package/skills/flutter-senior-review/AGENTS.md +0 -869
- package/skills/flutter-senior-review/principles/dependencies-data-not-callbacks.md +0 -75
- package/skills/flutter-senior-review/principles/dependencies-provider-tree.md +0 -85
- package/skills/flutter-senior-review/principles/dependencies-temporal-coupling.md +0 -97
- package/skills/flutter-senior-review/principles/pragmatism-consistent-error-handling.md +0 -130
- package/skills/flutter-senior-review/principles/pragmatism-speculative-generality.md +0 -91
- package/skills/flutter-senior-review/principles/state-data-clumps.md +0 -64
- package/skills/flutter-senior-review/principles/state-invalid-states.md +0 -53
- package/skills/flutter-senior-review/principles/state-single-source-of-truth.md +0 -68
- package/skills/flutter-senior-review/principles/state-type-hierarchies.md +0 -75
- package/skills/flutter-senior-review/principles/structure-composition-over-config.md +0 -105
- package/skills/flutter-senior-review/principles/structure-shared-visual-patterns.md +0 -107
- package/skills/flutter-senior-review/principles/structure-wrapper-pattern.md +0 -90
|
@@ -1,75 +0,0 @@
|
|
|
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?
|
|
@@ -1,105 +0,0 @@
|
|
|
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?
|
|
@@ -1,107 +0,0 @@
|
|
|
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?
|
|
@@ -1,90 +0,0 @@
|
|
|
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?
|