oh-my-customcode 0.36.1 → 0.37.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.
- package/dist/cli/index.js +47 -2
- package/dist/index.js +44 -0
- package/package.json +1 -1
- package/templates/.claude/agents/arch-documenter.md +4 -1
- package/templates/.claude/agents/arch-speckit-agent.md +15 -0
- package/templates/.claude/agents/be-django-expert.md +1 -0
- package/templates/.claude/agents/be-express-expert.md +1 -0
- package/templates/.claude/agents/be-fastapi-expert.md +1 -0
- package/templates/.claude/agents/be-go-backend-expert.md +1 -0
- package/templates/.claude/agents/be-nestjs-expert.md +1 -0
- package/templates/.claude/agents/be-springboot-expert.md +1 -0
- package/templates/.claude/agents/db-postgres-expert.md +1 -0
- package/templates/.claude/agents/db-redis-expert.md +1 -0
- package/templates/.claude/agents/db-supabase-expert.md +1 -0
- package/templates/.claude/agents/de-airflow-expert.md +1 -0
- package/templates/.claude/agents/de-dbt-expert.md +1 -0
- package/templates/.claude/agents/de-kafka-expert.md +1 -0
- package/templates/.claude/agents/de-pipeline-expert.md +1 -0
- package/templates/.claude/agents/de-snowflake-expert.md +1 -0
- package/templates/.claude/agents/de-spark-expert.md +1 -0
- package/templates/.claude/agents/fe-flutter-agent.md +1 -0
- package/templates/.claude/agents/fe-svelte-agent.md +1 -0
- package/templates/.claude/agents/fe-vercel-agent.md +1 -0
- package/templates/.claude/agents/fe-vuejs-agent.md +1 -0
- package/templates/.claude/agents/infra-aws-expert.md +1 -0
- package/templates/.claude/agents/infra-docker-expert.md +1 -0
- package/templates/.claude/agents/lang-golang-expert.md +1 -0
- package/templates/.claude/agents/lang-java21-expert.md +3 -0
- package/templates/.claude/agents/lang-kotlin-expert.md +1 -0
- package/templates/.claude/agents/lang-python-expert.md +1 -0
- package/templates/.claude/agents/lang-rust-expert.md +1 -0
- package/templates/.claude/agents/lang-typescript-expert.md +1 -0
- package/templates/.claude/agents/mgr-claude-code-bible.md +1 -2
- package/templates/.claude/agents/mgr-creator.md +1 -0
- package/templates/.claude/agents/mgr-gitnerd.md +1 -0
- package/templates/.claude/agents/mgr-sauron.md +5 -2
- package/templates/.claude/agents/mgr-supplier.md +1 -3
- package/templates/.claude/agents/mgr-updater.md +1 -0
- package/templates/.claude/agents/qa-engineer.md +1 -0
- package/templates/.claude/agents/qa-planner.md +4 -1
- package/templates/.claude/agents/qa-writer.md +1 -1
- package/templates/.claude/agents/sec-codeql-expert.md +4 -2
- package/templates/.claude/agents/sys-memory-keeper.md +30 -0
- package/templates/.claude/agents/sys-naggy.md +36 -2
- package/templates/.claude/agents/tool-bun-expert.md +1 -1
- package/templates/.claude/agents/tool-npm-expert.md +1 -1
- package/templates/.claude/agents/tool-optimizer.md +1 -2
- package/templates/.claude/hooks/hooks.json +37 -7
- package/templates/.claude/hooks/scripts/agent-teams-advisor.sh +10 -0
- package/templates/.claude/hooks/scripts/audit-log.sh +55 -0
- package/templates/.claude/hooks/scripts/content-hash-validator.sh +2 -3
- package/templates/.claude/hooks/scripts/schema-validator.sh +103 -0
- package/templates/.claude/hooks/scripts/secret-filter.sh +97 -0
- package/templates/.claude/hooks/scripts/session-compliance-report.sh +65 -0
- package/templates/.claude/rules/MUST-agent-teams.md +0 -23
- package/templates/.claude/rules/MUST-orchestrator-coordination.md +1 -13
- package/templates/.claude/skills/django-best-practices/SKILL.md +27 -134
- package/templates/.claude/skills/flutter-best-practices/SKILL.md +39 -146
- package/templates/.claude/skills/go-backend-best-practices/SKILL.md +29 -233
- package/templates/.claude/skills/java21-best-practices/SKILL.md +48 -163
- package/templates/CLAUDE.md.en +7 -65
- package/templates/CLAUDE.md.ko +7 -65
- package/templates/manifest.json +1 -1
|
@@ -16,26 +16,7 @@ Apply Django patterns for building production-ready, secure, and maintainable Py
|
|
|
16
16
|
```yaml
|
|
17
17
|
structure:
|
|
18
18
|
settings_split: true
|
|
19
|
-
layout:
|
|
20
|
-
project/
|
|
21
|
-
├── config/
|
|
22
|
-
│ ├── settings/
|
|
23
|
-
│ │ ├── base.py
|
|
24
|
-
│ │ ├── development.py
|
|
25
|
-
│ │ └── production.py
|
|
26
|
-
│ ├── urls.py
|
|
27
|
-
│ └── wsgi.py
|
|
28
|
-
├── apps/
|
|
29
|
-
│ ├── core/ # Shared utilities, base models
|
|
30
|
-
│ ├── users/ # Custom User model (ALWAYS create)
|
|
31
|
-
│ └── {feature}/ # Feature-specific apps
|
|
32
|
-
├── templates/
|
|
33
|
-
├── static/
|
|
34
|
-
├── requirements/
|
|
35
|
-
│ ├── base.txt
|
|
36
|
-
│ ├── development.txt
|
|
37
|
-
│ └── production.txt
|
|
38
|
-
└── manage.py
|
|
19
|
+
layout: "config/{settings/{base,development,production}.py,urls.py,wsgi.py} + apps/{core/,users/,<feature>/} + templates/ + static/ + requirements/{base,development,production}.txt"
|
|
39
20
|
|
|
40
21
|
app_module_contents:
|
|
41
22
|
models.py: Database models
|
|
@@ -50,6 +31,8 @@ app_module_contents:
|
|
|
50
31
|
tests/: Test suite (mirror app structure)
|
|
51
32
|
```
|
|
52
33
|
|
|
34
|
+
Reference: guides/django-best-practices/README.md
|
|
35
|
+
|
|
53
36
|
### 2. Models Best Practices
|
|
54
37
|
|
|
55
38
|
```yaml
|
|
@@ -57,15 +40,7 @@ custom_user_model:
|
|
|
57
40
|
rule: ALWAYS create a custom User model, even if identical to default
|
|
58
41
|
location: apps/users/models.py
|
|
59
42
|
reason: Impossible to swap default User model mid-project
|
|
60
|
-
|
|
61
|
-
# apps/users/models.py
|
|
62
|
-
from django.contrib.auth.models import AbstractUser
|
|
63
|
-
|
|
64
|
-
class User(AbstractUser):
|
|
65
|
-
pass # Can extend later without migrations
|
|
66
|
-
|
|
67
|
-
# config/settings/base.py
|
|
68
|
-
AUTH_USER_MODEL = 'users.User'
|
|
43
|
+
pattern: "Extend AbstractUser, set AUTH_USER_MODEL in settings"
|
|
69
44
|
|
|
70
45
|
primary_key:
|
|
71
46
|
default: BigAutoField
|
|
@@ -77,18 +52,6 @@ model_meta:
|
|
|
77
52
|
- Meta.ordering: consistent default ordering
|
|
78
53
|
- Meta.verbose_name: singular display name
|
|
79
54
|
- Meta.verbose_name_plural: plural display name
|
|
80
|
-
example: |
|
|
81
|
-
class Article(models.Model):
|
|
82
|
-
title = models.CharField(max_length=200)
|
|
83
|
-
created_at = models.DateTimeField(auto_now_add=True)
|
|
84
|
-
|
|
85
|
-
class Meta:
|
|
86
|
-
ordering = ['-created_at']
|
|
87
|
-
verbose_name = 'article'
|
|
88
|
-
verbose_name_plural = 'articles'
|
|
89
|
-
|
|
90
|
-
def __str__(self):
|
|
91
|
-
return self.title
|
|
92
55
|
|
|
93
56
|
query_optimization:
|
|
94
57
|
foreign_key: select_related() # Single SQL JOIN
|
|
@@ -107,18 +70,7 @@ indexing:
|
|
|
107
70
|
|
|
108
71
|
constraints:
|
|
109
72
|
use: Meta.constraints for database-level enforcement
|
|
110
|
-
|
|
111
|
-
class Meta:
|
|
112
|
-
constraints = [
|
|
113
|
-
models.UniqueConstraint(
|
|
114
|
-
fields=['user', 'article'],
|
|
115
|
-
name='unique_user_article'
|
|
116
|
-
),
|
|
117
|
-
models.CheckConstraint(
|
|
118
|
-
check=models.Q(price__gte=0),
|
|
119
|
-
name='price_non_negative'
|
|
120
|
-
)
|
|
121
|
-
]
|
|
73
|
+
types: "UniqueConstraint, CheckConstraint"
|
|
122
74
|
|
|
123
75
|
soft_delete:
|
|
124
76
|
pattern: is_active = models.BooleanField(default=True)
|
|
@@ -126,16 +78,10 @@ soft_delete:
|
|
|
126
78
|
|
|
127
79
|
custom_managers:
|
|
128
80
|
rule: Use managers for reusable querysets
|
|
129
|
-
example: |
|
|
130
|
-
class PublishedManager(models.Manager):
|
|
131
|
-
def get_queryset(self):
|
|
132
|
-
return super().get_queryset().filter(status='published')
|
|
133
|
-
|
|
134
|
-
class Article(models.Model):
|
|
135
|
-
objects = models.Manager() # Keep default
|
|
136
|
-
published = PublishedManager() # Add custom
|
|
137
81
|
```
|
|
138
82
|
|
|
83
|
+
Reference: guides/django-best-practices/README.md
|
|
84
|
+
|
|
139
85
|
### 3. Views Best Practices
|
|
140
86
|
|
|
141
87
|
```yaml
|
|
@@ -145,15 +91,6 @@ cbv_vs_fbv:
|
|
|
145
91
|
|
|
146
92
|
thin_views:
|
|
147
93
|
rule: Keep views thin — delegate business logic to services/models
|
|
148
|
-
wrong: |
|
|
149
|
-
def create_order(request):
|
|
150
|
-
# 50 lines of business logic in view
|
|
151
|
-
correct: |
|
|
152
|
-
def create_order(request):
|
|
153
|
-
form = OrderForm(request.POST)
|
|
154
|
-
if form.is_valid():
|
|
155
|
-
order = order_service.create(request.user, form.cleaned_data)
|
|
156
|
-
return redirect('order-detail', pk=order.pk)
|
|
157
94
|
|
|
158
95
|
shortcuts:
|
|
159
96
|
- get_object_or_404(Model, pk=pk): Returns 404 instead of 500
|
|
@@ -179,7 +116,7 @@ status_codes:
|
|
|
179
116
|
```yaml
|
|
180
117
|
namespacing:
|
|
181
118
|
app_name: Required in every app's urls.py
|
|
182
|
-
usage: reverse('app_name:url_name') or {% url 'app_name:url_name' %}
|
|
119
|
+
usage: "reverse('app_name:url_name') or {% url 'app_name:url_name' %}"
|
|
183
120
|
|
|
184
121
|
syntax:
|
|
185
122
|
prefer: path() over re_path() for clarity
|
|
@@ -187,56 +124,29 @@ syntax:
|
|
|
187
124
|
|
|
188
125
|
naming:
|
|
189
126
|
rule: Name ALL URL patterns
|
|
190
|
-
convention: "{resource}-{action}
|
|
127
|
+
convention: "{resource}-{action} (e.g., article-list, article-detail)"
|
|
191
128
|
|
|
192
129
|
inclusion:
|
|
193
130
|
root_urls: Use include() for app-level URLs
|
|
194
|
-
example: |
|
|
195
|
-
# config/urls.py
|
|
196
|
-
urlpatterns = [
|
|
197
|
-
path('admin/', admin.site.urls),
|
|
198
|
-
path('api/', include('apps.api.urls', namespace='api')),
|
|
199
|
-
path('articles/', include('apps.articles.urls', namespace='articles')),
|
|
200
|
-
]
|
|
201
|
-
|
|
202
|
-
# apps/articles/urls.py
|
|
203
|
-
app_name = 'articles'
|
|
204
|
-
urlpatterns = [
|
|
205
|
-
path('', ArticleListView.as_view(), name='list'),
|
|
206
|
-
path('<int:pk>/', ArticleDetailView.as_view(), name='detail'),
|
|
207
|
-
]
|
|
208
131
|
```
|
|
209
132
|
|
|
133
|
+
Reference: guides/django-best-practices/README.md
|
|
134
|
+
|
|
210
135
|
### 5. Forms & Validation
|
|
211
136
|
|
|
212
137
|
```yaml
|
|
213
138
|
model_forms:
|
|
214
139
|
rule: Use ModelForm when form maps to a model
|
|
215
|
-
fields: Explicitly list fields (never use fields = '__all__')
|
|
140
|
+
fields: "Explicitly list fields (never use fields = '__all__')"
|
|
216
141
|
|
|
217
142
|
validation:
|
|
218
143
|
field_level: clean_<field>() method
|
|
219
144
|
cross_field: clean() method
|
|
220
145
|
built_in: Use Django validators (MaxValueValidator, RegexValidator, etc.)
|
|
221
|
-
|
|
222
|
-
example: |
|
|
223
|
-
class ArticleForm(forms.ModelForm):
|
|
224
|
-
class Meta:
|
|
225
|
-
model = Article
|
|
226
|
-
fields = ['title', 'body', 'status']
|
|
227
|
-
|
|
228
|
-
def clean_title(self):
|
|
229
|
-
title = self.cleaned_data['title']
|
|
230
|
-
if len(title) < 5:
|
|
231
|
-
raise forms.ValidationError('Title too short.')
|
|
232
|
-
return title
|
|
233
|
-
|
|
234
|
-
def clean(self):
|
|
235
|
-
cleaned = super().clean()
|
|
236
|
-
# Cross-field validation here
|
|
237
|
-
return cleaned
|
|
238
146
|
```
|
|
239
147
|
|
|
148
|
+
Reference: guides/django-best-practices/README.md
|
|
149
|
+
|
|
240
150
|
### 6. Security
|
|
241
151
|
|
|
242
152
|
```yaml
|
|
@@ -287,22 +197,13 @@ test_classes:
|
|
|
287
197
|
test_data:
|
|
288
198
|
preferred: factory_boy or model_bakery
|
|
289
199
|
avoid: fixtures (hard to maintain, slow)
|
|
290
|
-
example: |
|
|
291
|
-
import factory
|
|
292
|
-
from apps.users.models import User
|
|
293
|
-
|
|
294
|
-
class UserFactory(factory.django.DjangoModelFactory):
|
|
295
|
-
class Meta:
|
|
296
|
-
model = User
|
|
297
|
-
username = factory.Sequence(lambda n: f'user{n}')
|
|
298
|
-
email = factory.LazyAttribute(lambda o: f'{o.username}@example.com')
|
|
299
200
|
|
|
300
201
|
request_testing:
|
|
301
202
|
Client: Full request/response cycle (preferred for views)
|
|
302
203
|
RequestFactory: Faster, no middleware (for unit testing views)
|
|
303
204
|
|
|
304
205
|
settings_override:
|
|
305
|
-
decorator: '@override_settings(
|
|
206
|
+
decorator: '@override_settings(...)'
|
|
306
207
|
|
|
307
208
|
coverage:
|
|
308
209
|
target: 80%+
|
|
@@ -312,6 +213,8 @@ structure:
|
|
|
312
213
|
mirror_app: tests/test_models.py, tests/test_views.py, tests/test_forms.py
|
|
313
214
|
```
|
|
314
215
|
|
|
216
|
+
Reference: guides/django-best-practices/README.md
|
|
217
|
+
|
|
315
218
|
### 8. Performance
|
|
316
219
|
|
|
317
220
|
```yaml
|
|
@@ -322,15 +225,15 @@ n_plus_1_prevention:
|
|
|
322
225
|
complex: Prefetch object with custom queryset
|
|
323
226
|
|
|
324
227
|
partial_loading:
|
|
325
|
-
only: only('id', 'title', 'created_at')
|
|
326
|
-
defer: defer('body', 'metadata')
|
|
327
|
-
values: values('id', 'title')
|
|
328
|
-
values_list: values_list('id', flat=True)
|
|
228
|
+
only: "only('id', 'title', 'created_at') — Load only these fields"
|
|
229
|
+
defer: "defer('body', 'metadata') — Load all except these"
|
|
230
|
+
values: "values('id', 'title') — Returns dicts (no ORM overhead)"
|
|
231
|
+
values_list: "values_list('id', flat=True) — Returns flat list"
|
|
329
232
|
|
|
330
233
|
caching:
|
|
331
234
|
backend: Redis (preferred), Memcached
|
|
332
|
-
view_cache: '@cache_page(60 * 15)' decorator
|
|
333
|
-
template_cache: '{% cache 500 sidebar %}' template tag
|
|
235
|
+
view_cache: "'@cache_page(60 * 15)' decorator"
|
|
236
|
+
template_cache: "'{% cache 500 sidebar %}' template tag"
|
|
334
237
|
low_level: cache.get/set/delete for fine-grained control
|
|
335
238
|
|
|
336
239
|
pagination:
|
|
@@ -339,8 +242,8 @@ pagination:
|
|
|
339
242
|
drf: PageNumberPagination or CursorPagination
|
|
340
243
|
|
|
341
244
|
bulk_operations:
|
|
342
|
-
create:
|
|
343
|
-
update:
|
|
245
|
+
create: "bulk_create(articles, batch_size=1000)"
|
|
246
|
+
update: "bulk_update(articles, ['status'], batch_size=1000)"
|
|
344
247
|
avoid: Loops calling .save() on many objects
|
|
345
248
|
```
|
|
346
249
|
|
|
@@ -351,17 +254,6 @@ serializers:
|
|
|
351
254
|
standard_crud: ModelSerializer
|
|
352
255
|
read_only: Use SerializerMethodField for computed values
|
|
353
256
|
write_validation: validate_<field>() and validate() methods
|
|
354
|
-
example: |
|
|
355
|
-
class ArticleSerializer(serializers.ModelSerializer):
|
|
356
|
-
author_name = serializers.SerializerMethodField()
|
|
357
|
-
|
|
358
|
-
class Meta:
|
|
359
|
-
model = Article
|
|
360
|
-
fields = ['id', 'title', 'body', 'author_name', 'created_at']
|
|
361
|
-
read_only_fields = ['id', 'created_at']
|
|
362
|
-
|
|
363
|
-
def get_author_name(self, obj):
|
|
364
|
-
return obj.author.get_full_name()
|
|
365
257
|
|
|
366
258
|
viewsets:
|
|
367
259
|
standard: ModelViewSet for full CRUD
|
|
@@ -380,7 +272,6 @@ permissions:
|
|
|
380
272
|
|
|
381
273
|
versioning:
|
|
382
274
|
method: NamespaceVersioning or URLPathVersioning
|
|
383
|
-
example: "/api/v1/articles/" vs "/api/v2/articles/"
|
|
384
275
|
|
|
385
276
|
throttling:
|
|
386
277
|
anonymous: AnonRateThrottle
|
|
@@ -392,6 +283,8 @@ pagination:
|
|
|
392
283
|
types: PageNumberPagination (simple), CursorPagination (large datasets)
|
|
393
284
|
```
|
|
394
285
|
|
|
286
|
+
Reference: guides/django-best-practices/README.md
|
|
287
|
+
|
|
395
288
|
### 10. Deployment
|
|
396
289
|
|
|
397
290
|
```yaml
|
|
@@ -46,15 +46,7 @@ state_choice:
|
|
|
46
46
|
|
|
47
47
|
build_context:
|
|
48
48
|
rule: "Never store BuildContext across async gaps"
|
|
49
|
-
pattern:
|
|
50
|
-
// BAD
|
|
51
|
-
final ctx = context;
|
|
52
|
-
await Future.delayed(Duration(seconds: 1));
|
|
53
|
-
Navigator.of(ctx).push(...); // context may be invalid
|
|
54
|
-
|
|
55
|
-
// GOOD
|
|
56
|
-
if (!mounted) return;
|
|
57
|
-
Navigator.of(context).push(...);
|
|
49
|
+
pattern: "Check mounted before using context after await"
|
|
58
50
|
|
|
59
51
|
keys:
|
|
60
52
|
ValueKey: "When items have unique business identity"
|
|
@@ -83,6 +75,8 @@ slivers:
|
|
|
83
75
|
avoid: "Nested ListView in ListView"
|
|
84
76
|
```
|
|
85
77
|
|
|
78
|
+
Reference: guides/flutter/fundamentals.md
|
|
79
|
+
|
|
86
80
|
### 2. State Management
|
|
87
81
|
|
|
88
82
|
```yaml
|
|
@@ -99,51 +93,26 @@ riverpod_patterns:
|
|
|
99
93
|
async_state: "AsyncNotifier + AsyncValue (loading/data/error)"
|
|
100
94
|
family: "family modifier for parameterized providers"
|
|
101
95
|
keep_alive: "Only when justified (expensive computations)"
|
|
102
|
-
invalidate_vs_refresh:
|
|
103
|
-
ref.invalidate(provider) // reset to loading, lazy re-fetch
|
|
104
|
-
ref.refresh(provider) // immediate re-fetch, return new value
|
|
96
|
+
invalidate_vs_refresh: "ref.invalidate() resets to loading (lazy); ref.refresh() immediate re-fetch"
|
|
105
97
|
|
|
106
98
|
bloc_patterns:
|
|
107
99
|
one_event_per_action: "One UI action = one event class"
|
|
108
|
-
cubit_vs_bloc:
|
|
109
|
-
Cubit: direct emit(state) — for simple state changes
|
|
110
|
-
Bloc: event → state mapping — when audit trail needed
|
|
100
|
+
cubit_vs_bloc: "Cubit for simple state changes; Bloc when audit trail needed"
|
|
111
101
|
never: "Emit state in constructor body"
|
|
112
|
-
listener_vs_consumer:
|
|
113
|
-
BlocListener: side effects (navigation, snackbar)
|
|
114
|
-
BlocConsumer: rebuild UI + side effects
|
|
115
|
-
BlocBuilder: rebuild UI only (most common)
|
|
102
|
+
listener_vs_consumer: "BlocListener for side effects; BlocConsumer for UI + effects; BlocBuilder for UI only"
|
|
116
103
|
stream_management: "Cancel subscriptions in close()"
|
|
117
104
|
|
|
118
105
|
state_immutability:
|
|
119
106
|
rule: "All state objects must be immutable"
|
|
120
107
|
tool: "freezed package for copyWith/==/hashCode generation"
|
|
121
|
-
pattern: |
|
|
122
|
-
@freezed
|
|
123
|
-
class UserState with _$UserState {
|
|
124
|
-
const factory UserState({
|
|
125
|
-
required String name,
|
|
126
|
-
required int age,
|
|
127
|
-
@Default(false) bool isLoading,
|
|
128
|
-
}) = _UserState;
|
|
129
|
-
}
|
|
130
108
|
|
|
131
109
|
result_type:
|
|
132
110
|
rule: "Return Result<T> from repositories, never throw"
|
|
133
|
-
pattern:
|
|
134
|
-
sealed class Result<T> {
|
|
135
|
-
const Result();
|
|
136
|
-
}
|
|
137
|
-
final class Ok<T> extends Result<T> {
|
|
138
|
-
const Ok(this.value);
|
|
139
|
-
final T value;
|
|
140
|
-
}
|
|
141
|
-
final class Error<T> extends Result<T> {
|
|
142
|
-
const Error(this.error);
|
|
143
|
-
final Exception error;
|
|
144
|
-
}
|
|
111
|
+
pattern: "sealed class Result<T> with Ok<T> and Error<T> subclasses"
|
|
145
112
|
```
|
|
146
113
|
|
|
114
|
+
Reference: guides/flutter/state-management.md
|
|
115
|
+
|
|
147
116
|
### 3. Performance
|
|
148
117
|
|
|
149
118
|
```yaml
|
|
@@ -151,16 +120,7 @@ build_optimization:
|
|
|
151
120
|
const_widgets: "Mark immutable widgets const — zero rebuild"
|
|
152
121
|
localize_setState: "Call setState on smallest possible subtree"
|
|
153
122
|
extract_widgets: "StatelessWidget class > helper method"
|
|
154
|
-
child_parameter:
|
|
155
|
-
// GOOD: static child passed through
|
|
156
|
-
AnimatedBuilder(
|
|
157
|
-
animation: controller,
|
|
158
|
-
builder: (context, child) => Transform.rotate(
|
|
159
|
-
angle: controller.value,
|
|
160
|
-
child: child, // not rebuilt
|
|
161
|
-
),
|
|
162
|
-
child: const ExpensiveWidget(), // built once
|
|
163
|
-
)
|
|
123
|
+
child_parameter: "Pass static child through AnimatedBuilder to avoid rebuild"
|
|
164
124
|
|
|
165
125
|
rebuild_avoidance:
|
|
166
126
|
consumer_placement: "Place Consumer/ListenableBuilder as deep as possible"
|
|
@@ -183,6 +143,8 @@ frame_budget:
|
|
|
183
143
|
tool: "DevTools Performance view for jank detection"
|
|
184
144
|
```
|
|
185
145
|
|
|
146
|
+
Reference: guides/flutter/performance.md
|
|
147
|
+
|
|
186
148
|
### 4. Testing
|
|
187
149
|
|
|
188
150
|
```yaml
|
|
@@ -192,36 +154,16 @@ test_pyramid:
|
|
|
192
154
|
integration: "Full app on device — slow, high confidence"
|
|
193
155
|
golden: "Visual regression via matchesGoldenFile()"
|
|
194
156
|
|
|
195
|
-
widget_test_pattern:
|
|
196
|
-
|
|
197
|
-
await tester.pumpWidget(
|
|
198
|
-
ProviderScope(
|
|
199
|
-
overrides: [productsProvider.overrideWith((_) => fakeProducts)],
|
|
200
|
-
child: const MaterialApp(home: ProductListScreen()),
|
|
201
|
-
),
|
|
202
|
-
);
|
|
203
|
-
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
|
204
|
-
await tester.pumpAndSettle();
|
|
205
|
-
expect(find.byType(ProductCard), findsNWidgets(3));
|
|
206
|
-
});
|
|
157
|
+
widget_test_pattern:
|
|
158
|
+
rule: "Use pumpWidget with ProviderScope overrides, then pump/pumpAndSettle for async"
|
|
207
159
|
|
|
208
160
|
mocking:
|
|
209
161
|
prefer: "mocktail (null-safe, no codegen)"
|
|
210
162
|
avoid: "Legacy mockito with build_runner"
|
|
211
163
|
fakes: "Use Fake implementations for deterministic tests"
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
Future<Result<List<Product>>> getAll() async => Ok(testProducts);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
bloc_testing: |
|
|
219
|
-
blocTest<AuthBloc, AuthState>(
|
|
220
|
-
'emits [loading, success] when login succeeds',
|
|
221
|
-
build: () => AuthBloc(FakeAuthRepository()),
|
|
222
|
-
act: (bloc) => bloc.add(LoginRequested('user', 'pass')),
|
|
223
|
-
expect: () => [AuthLoading(), isA<AuthSuccess>()],
|
|
224
|
-
);
|
|
164
|
+
|
|
165
|
+
bloc_testing:
|
|
166
|
+
rule: "Use blocTest<Bloc, State> with build/act/expect pattern"
|
|
225
167
|
|
|
226
168
|
coverage_target:
|
|
227
169
|
widget_tests: "80%+ for UI logic"
|
|
@@ -229,6 +171,8 @@ coverage_target:
|
|
|
229
171
|
integration: "Critical user flows only"
|
|
230
172
|
```
|
|
231
173
|
|
|
174
|
+
Reference: guides/flutter/testing.md
|
|
175
|
+
|
|
232
176
|
### 5. Security
|
|
233
177
|
|
|
234
178
|
```yaml
|
|
@@ -236,14 +180,14 @@ secrets:
|
|
|
236
180
|
never: "Hardcode API keys, tokens, or credentials in source"
|
|
237
181
|
best: "Backend proxy for all sensitive API calls"
|
|
238
182
|
use: "--dart-define-from-file=.env for NON-SECRET build config only (feature flags, environment URLs)"
|
|
239
|
-
warning: "dart-define values are embedded in compiled binary and extractable via static analysis
|
|
183
|
+
warning: "dart-define values are embedded in compiled binary and extractable via static analysis"
|
|
240
184
|
|
|
241
185
|
storage:
|
|
242
186
|
sensitive_data: "flutter_secure_storage v10+ (Keychain/Keystore)"
|
|
243
187
|
never: "SharedPreferences for tokens, PII, or credentials"
|
|
244
188
|
ios: "AppleOptions(useSecureEnclave: true) for high-value"
|
|
245
189
|
android: "AndroidOptions(encryptedSharedPreferences: true)"
|
|
246
|
-
web_warning: "flutter_secure_storage on Web uses localStorage by default
|
|
190
|
+
web_warning: "flutter_secure_storage on Web uses localStorage by default (XSS vulnerable). Use HttpOnly cookies or in-memory-only for sensitive data."
|
|
247
191
|
|
|
248
192
|
network:
|
|
249
193
|
tls: "Certificate pinning (SPKI) for financial/health apps"
|
|
@@ -267,54 +211,35 @@ logging:
|
|
|
267
211
|
never: "Log PII, tokens, or credentials"
|
|
268
212
|
```
|
|
269
213
|
|
|
214
|
+
Reference: guides/flutter/security.md
|
|
215
|
+
|
|
270
216
|
### 6. Dart Language Patterns
|
|
271
217
|
|
|
272
218
|
```yaml
|
|
273
219
|
naming:
|
|
274
|
-
types: "UpperCamelCase for classes, enums, typedefs, extensions, mixins
|
|
275
|
-
variables: "lowerCamelCase for variables, parameters, named constants
|
|
276
|
-
libraries: "lowercase_with_underscores for libraries, packages, directories, source files
|
|
277
|
-
constants: "lowerCamelCase for const (
|
|
278
|
-
private: "Prefix with underscore for library-private
|
|
279
|
-
boolean: "Prefix with is/has/can/should
|
|
280
|
-
avoid: "Hungarian notation, type prefixes
|
|
220
|
+
types: "UpperCamelCase for classes, enums, typedefs, extensions, mixins"
|
|
221
|
+
variables: "lowerCamelCase for variables, parameters, named constants"
|
|
222
|
+
libraries: "lowercase_with_underscores for libraries, packages, directories, source files"
|
|
223
|
+
constants: "lowerCamelCase for const (NOT SCREAMING_CAPS)"
|
|
224
|
+
private: "Prefix with underscore for library-private"
|
|
225
|
+
boolean: "Prefix with is/has/can/should"
|
|
226
|
+
avoid: "Hungarian notation, type prefixes, abbreviations unless universally known"
|
|
281
227
|
|
|
282
228
|
null_safety:
|
|
283
229
|
default: "Non-nullable types — use ? only when null is meaningful"
|
|
284
230
|
avoid_bang: "Minimize ! operator — use only when null is logically impossible"
|
|
285
231
|
late: "Only when initialization is guaranteed before use"
|
|
286
|
-
pattern: |
|
|
287
|
-
// GOOD
|
|
288
|
-
final name = user?.name ?? 'Anonymous';
|
|
289
|
-
|
|
290
|
-
// AVOID
|
|
291
|
-
final name = user!.name; // crashes if null
|
|
292
232
|
|
|
293
233
|
sealed_classes:
|
|
294
234
|
use_for: "Exhaustive pattern matching on state/result types"
|
|
295
|
-
pattern:
|
|
296
|
-
sealed class AuthState {}
|
|
297
|
-
class AuthInitial extends AuthState {}
|
|
298
|
-
class AuthLoading extends AuthState {}
|
|
299
|
-
class AuthSuccess extends AuthState { final User user; AuthSuccess(this.user); }
|
|
300
|
-
class AuthError extends AuthState { final String message; AuthError(this.message); }
|
|
301
|
-
|
|
302
|
-
// Exhaustive switch — compiler enforces all cases
|
|
303
|
-
return switch (state) {
|
|
304
|
-
AuthInitial() => LoginScreen(),
|
|
305
|
-
AuthLoading() => CircularProgressIndicator(),
|
|
306
|
-
AuthSuccess(:final user) => HomeScreen(user: user),
|
|
307
|
-
AuthError(:final message) => ErrorWidget(message),
|
|
308
|
-
};
|
|
235
|
+
pattern: "sealed class with subclass per state, exhaustive switch expression"
|
|
309
236
|
|
|
310
237
|
records:
|
|
311
238
|
use_for: "Lightweight multi-value returns without class boilerplate"
|
|
312
|
-
pattern: "(String name, int age) getUserInfo() => ('Alice', 30);"
|
|
313
239
|
avoid: "Records for complex data — use freezed classes instead"
|
|
314
240
|
|
|
315
241
|
extension_types:
|
|
316
242
|
use_for: "Zero-cost type wrappers for primitive IDs"
|
|
317
|
-
pattern: "extension type UserId(int id) implements int {}"
|
|
318
243
|
|
|
319
244
|
immutability:
|
|
320
245
|
prefer: "final variables, const constructors"
|
|
@@ -331,48 +256,19 @@ dynamic:
|
|
|
331
256
|
reason: "No compile-time type checking, reduces IDE support"
|
|
332
257
|
```
|
|
333
258
|
|
|
259
|
+
Reference: guides/flutter/fundamentals.md
|
|
260
|
+
|
|
334
261
|
### 7. Architecture & Project Structure
|
|
335
262
|
|
|
336
263
|
```yaml
|
|
337
264
|
default_structure:
|
|
338
|
-
small_app:
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
services/
|
|
342
|
-
screens/
|
|
343
|
-
widgets/
|
|
344
|
-
medium_app: |
|
|
345
|
-
lib/
|
|
346
|
-
ui/
|
|
347
|
-
core/themes/, core/widgets/
|
|
348
|
-
<feature>/<feature>_screen.dart
|
|
349
|
-
<feature>/<feature>_viewmodel.dart
|
|
350
|
-
data/
|
|
351
|
-
repositories/<entity>_repository.dart
|
|
352
|
-
services/<source>_service.dart
|
|
353
|
-
domain/ (optional)
|
|
354
|
-
use_cases/
|
|
355
|
-
large_app: |
|
|
356
|
-
lib/
|
|
357
|
-
core/ (shared)
|
|
358
|
-
features/<feature>/
|
|
359
|
-
data/datasources/, data/repositories/
|
|
360
|
-
domain/entities/, domain/usecases/
|
|
361
|
-
presentation/bloc/, presentation/pages/
|
|
265
|
+
small_app: "lib/{models,services,screens,widgets}/"
|
|
266
|
+
medium_app: "lib/{ui/{core/,<feature>/},data/{repositories/,services/},domain/}"
|
|
267
|
+
large_app: "lib/{core/,features/<feature>/{data/,domain/,presentation/}}"
|
|
362
268
|
|
|
363
269
|
navigation:
|
|
364
270
|
default: "go_router (official recommendation)"
|
|
365
|
-
|
|
366
|
-
GoRoute(
|
|
367
|
-
path: '/product/:id',
|
|
368
|
-
builder: (context, state) {
|
|
369
|
-
final id = state.pathParameters['id']!;
|
|
370
|
-
return ProductDetailScreen(id: id);
|
|
371
|
-
},
|
|
372
|
-
)
|
|
373
|
-
go_vs_push: |
|
|
374
|
-
context.go('/path') // replaces stack (navigation reset)
|
|
375
|
-
context.push('/path') // adds to stack (back button works)
|
|
271
|
+
go_vs_push: "context.go() replaces stack; context.push() adds to stack"
|
|
376
272
|
|
|
377
273
|
dependency_injection:
|
|
378
274
|
riverpod: "Built-in — providers as DI (default)"
|
|
@@ -381,13 +277,12 @@ dependency_injection:
|
|
|
381
277
|
|
|
382
278
|
environments:
|
|
383
279
|
pattern: "Flavors + --dart-define for multi-environment builds"
|
|
384
|
-
command: "flutter run --flavor development --target lib/main/main_development.dart"
|
|
385
280
|
rule: "Separate bundle IDs, API URLs, and Firebase config per flavor"
|
|
386
281
|
```
|
|
387
282
|
|
|
388
|
-
|
|
283
|
+
Reference: guides/flutter/architecture.md
|
|
389
284
|
|
|
390
|
-
|
|
285
|
+
## Default Stack
|
|
391
286
|
|
|
392
287
|
```yaml
|
|
393
288
|
state_management: Riverpod 3.0
|
|
@@ -402,8 +297,6 @@ structure: Official MVVM (lib/{ui,data}/)
|
|
|
402
297
|
|
|
403
298
|
## Enterprise Stack
|
|
404
299
|
|
|
405
|
-
For regulated or large-team projects:
|
|
406
|
-
|
|
407
300
|
```yaml
|
|
408
301
|
state_management: BLoC 9.0 + Cubit
|
|
409
302
|
navigation: go_router or auto_route
|