flutter-pro-max-cli 2.0.0 → 2.1.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.
@@ -0,0 +1,1074 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Flutter Pro Max Design System Generator - Aggregates search results and applies reasoning
5
+ to generate comprehensive design system recommendations for Flutter apps.
6
+
7
+ Usage:
8
+ from design_system import generate_design_system
9
+ result = generate_design_system("fintech banking app", "MyApp")
10
+
11
+ # With persistence (Master + Overrides pattern)
12
+ result = generate_design_system("fintech banking app", "MyApp", persist=True)
13
+ result = generate_design_system("fintech banking app", "MyApp", persist=True, page="dashboard")
14
+ """
15
+
16
+ import csv
17
+ import json
18
+ from datetime import datetime
19
+ from pathlib import Path
20
+ from typing import Any
21
+ from core import search, DATA_DIR
22
+
23
+
24
+ # ============ CONFIGURATION ============
25
+ REASONING_FILE = "ui-reasoning.csv"
26
+
27
+ SEARCH_CONFIG = {
28
+ "product": {"max_results": 1},
29
+ "style": {"max_results": 3},
30
+ "color": {"max_results": 2},
31
+ "typography": {"max_results": 2},
32
+ "pattern": {"max_results": 3},
33
+ "architect": {"max_results": 2},
34
+ "landing": {"max_results": 2} # Landing page patterns for screen structure
35
+ }
36
+
37
+
38
+ # ============ DESIGN SYSTEM GENERATOR ============
39
+ class DesignSystemGenerator:
40
+ """Generates design system recommendations from aggregated searches."""
41
+
42
+ def __init__(self) -> None:
43
+ self.reasoning_data: list[dict[str, str]] = self._load_reasoning()
44
+
45
+ def _load_reasoning(self) -> list[dict[str, str]]:
46
+ """Load reasoning rules from CSV."""
47
+ filepath = DATA_DIR / REASONING_FILE
48
+ if not filepath.exists():
49
+ return []
50
+ with open(filepath, 'r', encoding='utf-8') as f:
51
+ return list(csv.DictReader(f))
52
+
53
+ def _multi_domain_search(self, query: str, style_priority: list[str] | None = None) -> dict[str, Any]:
54
+ """Execute searches across multiple domains."""
55
+ results: dict[str, Any] = {}
56
+ for domain, config in SEARCH_CONFIG.items():
57
+ if domain == "style" and style_priority:
58
+ # For style, also search with priority keywords
59
+ priority_query = " ".join(style_priority[:2]) if style_priority else query
60
+ combined_query = f"{query} {priority_query}"
61
+ results[domain] = search(combined_query, domain, config["max_results"])
62
+ else:
63
+ results[domain] = search(query, domain, config["max_results"])
64
+ return results
65
+
66
+ def _find_reasoning_rule(self, category: str) -> dict[str, str]:
67
+ """Find matching reasoning rule for a category."""
68
+ category_lower = category.lower()
69
+
70
+ # Try exact match first
71
+ for rule in self.reasoning_data:
72
+ if rule.get("App_Category", "").lower() == category_lower:
73
+ return rule
74
+
75
+ # Try partial match
76
+ for rule in self.reasoning_data:
77
+ app_cat = rule.get("App_Category", "").lower()
78
+ if app_cat in category_lower or category_lower in app_cat:
79
+ return rule
80
+
81
+ # Try keyword match
82
+ for rule in self.reasoning_data:
83
+ app_cat = rule.get("App_Category", "").lower()
84
+ keywords = app_cat.replace("/", " ").replace("-", " ").split()
85
+ if any(kw in category_lower for kw in keywords):
86
+ return rule
87
+
88
+ return {}
89
+
90
+ def _apply_reasoning(self, category: str, search_results: dict[str, Any]) -> dict[str, Any]:
91
+ """Apply reasoning rules to search results with intelligent decision processing."""
92
+ rule = self._find_reasoning_rule(category)
93
+
94
+ if not rule:
95
+ return {
96
+ "pattern": "Clean Architecture + Feature-First",
97
+ "style_priority": ["Minimalism", "Flat Design"],
98
+ "color_mood": "Professional",
99
+ "typography_mood": "Clean",
100
+ "key_effects": "Subtle animations, smooth transitions",
101
+ "anti_patterns": "",
102
+ "decision_rules": {},
103
+ "severity": "MEDIUM",
104
+ "must_have_features": [],
105
+ "conversion_focus": ""
106
+ }
107
+
108
+ # Parse decision rules JSON and extract must_have features
109
+ decision_rules: dict[str, Any] = {}
110
+ must_have_features: list[Any] = []
111
+ try:
112
+ decision_rules = json.loads(str(rule.get("Decision_Rules", "{}")))
113
+ # Extract must_have from decision rules
114
+ for key, value in decision_rules.items():
115
+ if key == "must_have" or key.startswith("must_have"):
116
+ must_have_features.append(value)
117
+ except json.JSONDecodeError:
118
+ pass
119
+
120
+ # Determine conversion focus based on category
121
+ conversion_focus = self._determine_conversion_focus(category)
122
+
123
+ return {
124
+ "pattern": rule.get("Recommended_Pattern", ""),
125
+ "style_priority": [s.strip() for s in rule.get("Style_Priority", "").split("+")],
126
+ "color_mood": rule.get("Color_Mood", ""),
127
+ "typography_mood": rule.get("Typography_Mood", ""),
128
+ "key_effects": rule.get("Key_Effects", ""),
129
+ "anti_patterns": rule.get("Anti_Patterns", ""),
130
+ "decision_rules": decision_rules,
131
+ "severity": rule.get("Severity", "MEDIUM"),
132
+ "must_have_features": must_have_features,
133
+ "conversion_focus": conversion_focus
134
+ }
135
+
136
+ def _determine_conversion_focus(self, category: str) -> str:
137
+ """Determine conversion focus based on app category."""
138
+ category_lower = category.lower()
139
+
140
+ conversion_map: dict[str, str] = {
141
+ "e-commerce": "Purchase conversion, Add to cart, Quick checkout",
142
+ "fintech": "Trust building, Security perception, Transaction completion",
143
+ "banking": "Trust building, Security perception, Transaction completion",
144
+ "health": "Appointment booking, Trust signals, Accessibility",
145
+ "fitness": "Engagement, Progress motivation, Habit formation",
146
+ "social": "Engagement, Retention, Content sharing",
147
+ "education": "Progress tracking, Completion rates, Engagement",
148
+ "productivity": "Task completion, Efficiency, Quick actions",
149
+ "food": "Order conversion, Reorder, Quick checkout",
150
+ "travel": "Booking conversion, Search efficiency, Trust",
151
+ "gaming": "Engagement, Retention, In-app purchases",
152
+ }
153
+
154
+ for key, focus in conversion_map.items():
155
+ if key in category_lower:
156
+ return focus
157
+
158
+ return "User engagement and task completion"
159
+
160
+ def _select_best_match(self, results: list[dict[str, Any]], priority_keywords: list[str]) -> dict[str, Any]:
161
+ """Select best matching result based on priority keywords."""
162
+ if not results:
163
+ return {}
164
+
165
+ if not priority_keywords:
166
+ return results[0]
167
+
168
+ # First: try exact style name match
169
+ for priority in priority_keywords:
170
+ priority_lower = priority.lower().strip()
171
+ for result in results:
172
+ style_name = result.get("Style Category", "").lower()
173
+ if priority_lower in style_name or style_name in priority_lower:
174
+ return result
175
+
176
+ # Second: score by keyword match in all fields
177
+ scored: list[tuple[int, dict[str, Any]]] = []
178
+ for result in results:
179
+ result_str = str(result).lower()
180
+ score = 0
181
+ for kw in priority_keywords:
182
+ kw_lower = kw.lower().strip()
183
+ # Higher score for style name match
184
+ if kw_lower in result.get("Style Category", "").lower():
185
+ score += 10
186
+ # Lower score for keyword field match
187
+ elif kw_lower in result.get("Keywords", "").lower():
188
+ score += 3
189
+ # Even lower for other field matches
190
+ elif kw_lower in result_str:
191
+ score += 1
192
+ scored.append((score, result))
193
+
194
+ scored.sort(key=lambda x: x[0], reverse=True)
195
+ return scored[0][1] if scored and scored[0][0] > 0 else results[0]
196
+
197
+ def _extract_results(self, search_result: dict[str, Any]) -> list[dict[str, Any]]:
198
+ """Extract results list from search result dict."""
199
+ return search_result.get("results", [])
200
+
201
+ def generate(self, query: str, project_name: str | None = None) -> dict[str, Any]:
202
+ """Generate complete design system recommendation with landing pattern intelligence."""
203
+ # Step 1: First search product to get category
204
+ product_result = search(query, "product", 1)
205
+ product_results = product_result.get("results", [])
206
+ category = "General"
207
+ product_info = {}
208
+ if product_results:
209
+ product_info = product_results[0]
210
+ category = product_info.get("Product Type", "General")
211
+
212
+ # Step 2: Get reasoning rules for this category
213
+ reasoning = self._apply_reasoning(category, {})
214
+ style_priority = reasoning.get("style_priority", [])
215
+
216
+ # Step 3: Multi-domain search with style priority hints
217
+ search_results = self._multi_domain_search(query, style_priority)
218
+ search_results["product"] = product_result # Reuse product search
219
+
220
+ # Step 4: Select best matches from each domain using priority
221
+ style_results = self._extract_results(search_results.get("style", {}))
222
+ color_results = self._extract_results(search_results.get("color", {}))
223
+ typography_results = self._extract_results(search_results.get("typography", {}))
224
+ pattern_results = self._extract_results(search_results.get("pattern", {}))
225
+ architect_results = self._extract_results(search_results.get("architect", {}))
226
+ landing_results = self._extract_results(search_results.get("landing", {}))
227
+
228
+ best_style = self._select_best_match(style_results, reasoning.get("style_priority", []))
229
+ best_color = color_results[0] if color_results else {}
230
+ best_typography = typography_results[0] if typography_results else {}
231
+ best_landing = landing_results[0] if landing_results else {}
232
+
233
+ # Step 5: Build final recommendation
234
+ # Combine effects from both reasoning and style search
235
+ style_effects = best_style.get("Effects & Animation", "")
236
+ reasoning_effects = reasoning.get("key_effects", "")
237
+ combined_effects = style_effects if style_effects else reasoning_effects
238
+
239
+ # Get landing pattern info
240
+ landing_effects = best_landing.get("Recommended Effects", "")
241
+ if landing_effects and not combined_effects:
242
+ combined_effects = landing_effects
243
+
244
+ return {
245
+ "project_name": project_name or query.upper(),
246
+ "category": category,
247
+ "pattern": {
248
+ "name": reasoning.get("pattern", "Clean Architecture"),
249
+ "architecture": architect_results[0].get("layer", "") if architect_results else "Feature-First",
250
+ "state_management": "Riverpod / BLoC",
251
+ "recommended_patterns": [p.get("pattern_name", "") for p in pattern_results[:3]]
252
+ },
253
+ # NEW: Landing/Screen pattern from UI-UX Pro Max
254
+ "screen_pattern": {
255
+ "name": best_landing.get("Pattern Name", "Hero + Features + CTA"),
256
+ "sections": best_landing.get("Section Order", "Hero > Features > CTA"),
257
+ "cta_placement": best_landing.get("Primary CTA Placement", "Bottom + Sticky"),
258
+ "color_strategy": best_landing.get("Color Strategy", ""),
259
+ "conversion_optimization": best_landing.get("Conversion Optimization", "")
260
+ },
261
+ "style": {
262
+ "name": best_style.get("Style Category", "Minimalism"),
263
+ "type": best_style.get("Type", "General"),
264
+ "effects": style_effects,
265
+ "keywords": best_style.get("Keywords", ""),
266
+ "best_for": best_style.get("Best For", ""),
267
+ "do_not_use": best_style.get("Do Not Use For", "")
268
+ },
269
+ "colors": {
270
+ "primary": best_color.get("Primary (Hex)", "#2563EB"),
271
+ "secondary": best_color.get("Secondary (Hex)", "#3B82F6"),
272
+ "cta": best_color.get("CTA (Hex)", "#F97316"),
273
+ "background": "#FFFFFF",
274
+ "surface": "#F8FAFC",
275
+ "text": "#1E293B",
276
+ "notes": best_color.get("Notes", ""),
277
+ "strategy": best_landing.get("Color Strategy", reasoning.get("color_mood", ""))
278
+ },
279
+ "typography": {
280
+ "heading": best_typography.get("Heading Font", "Inter"),
281
+ "body": best_typography.get("Body Font", "Inter"),
282
+ "mood": best_typography.get("Mood/Style Keywords", reasoning.get("typography_mood", "")),
283
+ "best_for": best_typography.get("Best For", ""),
284
+ "google_fonts_url": best_typography.get("Google Fonts URL", "")
285
+ },
286
+ "key_effects": combined_effects,
287
+ "anti_patterns": reasoning.get("anti_patterns", ""),
288
+ "decision_rules": reasoning.get("decision_rules", {}),
289
+ "severity": reasoning.get("severity", "MEDIUM"),
290
+ # NEW: Conversion and must-have features
291
+ "conversion_focus": reasoning.get("conversion_focus", ""),
292
+ "must_have_features": reasoning.get("must_have_features", [])
293
+ }
294
+
295
+
296
+ # ============ OUTPUT FORMATTERS ============
297
+ BOX_WIDTH = 90
298
+
299
+ def format_ascii_box(design_system: dict[str, Any]) -> str:
300
+ """Format design system as ASCII box for Flutter apps with screen pattern intelligence."""
301
+ project = design_system.get("project_name", "PROJECT")
302
+ pattern = design_system.get("pattern", {})
303
+ screen_pattern = design_system.get("screen_pattern", {})
304
+ style = design_system.get("style", {})
305
+ colors = design_system.get("colors", {})
306
+ typography = design_system.get("typography", {})
307
+ effects = design_system.get("key_effects", "")
308
+ anti_patterns = design_system.get("anti_patterns", "")
309
+ conversion_focus = design_system.get("conversion_focus", "")
310
+ must_have_features = design_system.get("must_have_features", [])
311
+
312
+ def wrap_text(text: str, prefix: str, width: int) -> list[str]:
313
+ """Wrap long text into multiple lines."""
314
+ if not text:
315
+ return []
316
+ words = text.split()
317
+ lines: list[str] = []
318
+ current_line = prefix
319
+ for word in words:
320
+ if len(current_line) + len(word) + 1 <= width - 2:
321
+ current_line += (" " if current_line != prefix else "") + word
322
+ else:
323
+ if current_line != prefix:
324
+ lines.append(current_line)
325
+ current_line = prefix + word
326
+ if current_line != prefix:
327
+ lines.append(current_line)
328
+ return lines
329
+
330
+ # Build output lines
331
+ lines: list[str] = []
332
+ w = BOX_WIDTH - 1
333
+
334
+ lines.append("+" + "-" * w + "+")
335
+ lines.append(f"| TARGET: {project} - FLUTTER DESIGN SYSTEM".ljust(BOX_WIDTH) + "|")
336
+ lines.append("+" + "-" * w + "+")
337
+ lines.append("|" + " " * BOX_WIDTH + "|")
338
+
339
+ # NEW: Screen Pattern section (from UI-UX Pro Max)
340
+ if screen_pattern.get("name"):
341
+ lines.append(f"| SCREEN PATTERN: {screen_pattern.get('name', '')}".ljust(BOX_WIDTH) + "|")
342
+ if screen_pattern.get("sections"):
343
+ sections_str = screen_pattern.get("sections", "")[:65]
344
+ lines.append(f"| Sections: {sections_str}".ljust(BOX_WIDTH) + "|")
345
+ if screen_pattern.get("cta_placement"):
346
+ lines.append(f"| CTA: {screen_pattern.get('cta_placement', '')}".ljust(BOX_WIDTH) + "|")
347
+ if screen_pattern.get("conversion_optimization"):
348
+ for line in wrap_text(f"Conversion: {screen_pattern.get('conversion_optimization', '')}", "| ", BOX_WIDTH):
349
+ lines.append(line.ljust(BOX_WIDTH) + "|")
350
+ lines.append("|" + " " * BOX_WIDTH + "|")
351
+
352
+ # Architecture Pattern section
353
+ lines.append(f"| ARCHITECTURE: {pattern.get('name', '')}".ljust(BOX_WIDTH) + "|")
354
+ lines.append(f"| Structure: Feature-First / Clean Architecture".ljust(BOX_WIDTH) + "|")
355
+ lines.append(f"| State: {pattern.get('state_management', 'Riverpod')}".ljust(BOX_WIDTH) + "|")
356
+ if pattern.get('recommended_patterns'):
357
+ patterns_str = ", ".join(filter(None, pattern.get('recommended_patterns', [])))[:60]
358
+ lines.append(f"| Patterns: {patterns_str}".ljust(BOX_WIDTH) + "|")
359
+ lines.append("|" + " " * BOX_WIDTH + "|")
360
+
361
+ # Style section
362
+ lines.append(f"| UI STYLE: {style.get('name', '')}".ljust(BOX_WIDTH) + "|")
363
+ if style.get("keywords"):
364
+ for line in wrap_text(f"Keywords: {style.get('keywords', '')}", "| ", BOX_WIDTH):
365
+ lines.append(line.ljust(BOX_WIDTH) + "|")
366
+ if style.get("best_for"):
367
+ for line in wrap_text(f"Best For: {style.get('best_for', '')}", "| ", BOX_WIDTH):
368
+ lines.append(line.ljust(BOX_WIDTH) + "|")
369
+ lines.append("|" + " " * BOX_WIDTH + "|")
370
+
371
+ # Colors section
372
+ lines.append("| COLOR PALETTE:".ljust(BOX_WIDTH) + "|")
373
+ lines.append(f"| Primary: {colors.get('primary', '')}".ljust(BOX_WIDTH) + "|")
374
+ lines.append(f"| Secondary: {colors.get('secondary', '')}".ljust(BOX_WIDTH) + "|")
375
+ lines.append(f"| CTA: {colors.get('cta', '')}".ljust(BOX_WIDTH) + "|")
376
+ lines.append(f"| Background: {colors.get('background', '')}".ljust(BOX_WIDTH) + "|")
377
+ lines.append(f"| Surface: {colors.get('surface', '')}".ljust(BOX_WIDTH) + "|")
378
+ lines.append(f"| Text: {colors.get('text', '')}".ljust(BOX_WIDTH) + "|")
379
+ if colors.get("notes"):
380
+ for line in wrap_text(f"Notes: {colors.get('notes', '')}", "| ", BOX_WIDTH):
381
+ lines.append(line.ljust(BOX_WIDTH) + "|")
382
+ if colors.get("strategy"):
383
+ for line in wrap_text(f"Strategy: {colors.get('strategy', '')}", "| ", BOX_WIDTH):
384
+ lines.append(line.ljust(BOX_WIDTH) + "|")
385
+ lines.append("|" + " " * BOX_WIDTH + "|")
386
+
387
+ # Typography section
388
+ lines.append(f"| TYPOGRAPHY: {typography.get('heading', '')} / {typography.get('body', '')}".ljust(BOX_WIDTH) + "|")
389
+ if typography.get("mood"):
390
+ for line in wrap_text(f"Mood: {typography.get('mood', '')}", "| ", BOX_WIDTH):
391
+ lines.append(line.ljust(BOX_WIDTH) + "|")
392
+ if typography.get("best_for"):
393
+ for line in wrap_text(f"Best For: {typography.get('best_for', '')}", "| ", BOX_WIDTH):
394
+ lines.append(line.ljust(BOX_WIDTH) + "|")
395
+ lines.append("|" + " " * BOX_WIDTH + "|")
396
+
397
+ # Key Effects section
398
+ if effects:
399
+ lines.append("| KEY EFFECTS:".ljust(BOX_WIDTH) + "|")
400
+ for line in wrap_text(effects, "| ", BOX_WIDTH):
401
+ lines.append(line.ljust(BOX_WIDTH) + "|")
402
+ lines.append("|" + " " * BOX_WIDTH + "|")
403
+
404
+ # NEW: Conversion Focus section
405
+ if conversion_focus:
406
+ lines.append("| CONVERSION FOCUS:".ljust(BOX_WIDTH) + "|")
407
+ for line in wrap_text(conversion_focus, "| ", BOX_WIDTH):
408
+ lines.append(line.ljust(BOX_WIDTH) + "|")
409
+ lines.append("|" + " " * BOX_WIDTH + "|")
410
+
411
+ # NEW: Must-Have Features section
412
+ if must_have_features:
413
+ lines.append("| MUST-HAVE FEATURES:".ljust(BOX_WIDTH) + "|")
414
+ for feature in must_have_features:
415
+ lines.append(f"| ✓ {feature}".ljust(BOX_WIDTH) + "|")
416
+ lines.append("|" + " " * BOX_WIDTH + "|")
417
+
418
+ # Anti-patterns section
419
+ if anti_patterns:
420
+ lines.append("| AVOID (Anti-patterns):".ljust(BOX_WIDTH) + "|")
421
+ for line in wrap_text(anti_patterns, "| ", BOX_WIDTH):
422
+ lines.append(line.ljust(BOX_WIDTH) + "|")
423
+ lines.append("|" + " " * BOX_WIDTH + "|")
424
+
425
+ # Flutter-specific checklist
426
+ lines.append("| PRE-DELIVERY CHECKLIST:".ljust(BOX_WIDTH) + "|")
427
+ checklist_items = [
428
+ "[ ] const constructors for immutable widgets",
429
+ "[ ] Keys for lists and animated widgets",
430
+ "[ ] Proper widget decomposition (no God widgets)",
431
+ "[ ] Responsive: 375px, 768px, 1024px breakpoints",
432
+ "[ ] Accessibility: Semantics labels, touch targets >= 48px",
433
+ "[ ] Performance: ListView.builder for long lists",
434
+ "[ ] Error handling: proper error/loading states"
435
+ ]
436
+ for item in checklist_items:
437
+ lines.append(f"| {item}".ljust(BOX_WIDTH) + "|")
438
+ lines.append("|" + " " * BOX_WIDTH + "|")
439
+
440
+ lines.append("+" + "-" * w + "+")
441
+
442
+ return "\n".join(lines)
443
+
444
+
445
+ def format_markdown(design_system: dict[str, Any]) -> str:
446
+ """Format design system as markdown with screen pattern intelligence."""
447
+ project = design_system.get("project_name", "PROJECT")
448
+ pattern = design_system.get("pattern", {})
449
+ screen_pattern = design_system.get("screen_pattern", {})
450
+ style = design_system.get("style", {})
451
+ colors = design_system.get("colors", {})
452
+ typography = design_system.get("typography", {})
453
+ effects = design_system.get("key_effects", "")
454
+ anti_patterns = design_system.get("anti_patterns", "")
455
+ conversion_focus = design_system.get("conversion_focus", "")
456
+ must_have_features = design_system.get("must_have_features", [])
457
+
458
+ lines: list[str] = []
459
+ lines.append(f"## Flutter Design System: {project}")
460
+ lines.append("")
461
+
462
+ # NEW: Screen Pattern section (from UI-UX Pro Max)
463
+ if screen_pattern.get("name"):
464
+ lines.append("### Screen Pattern")
465
+ lines.append(f"- **Pattern:** {screen_pattern.get('name', '')}")
466
+ if screen_pattern.get("sections"):
467
+ lines.append(f"- **Sections:** {screen_pattern.get('sections', '')}")
468
+ if screen_pattern.get("cta_placement"):
469
+ lines.append(f"- **CTA Placement:** {screen_pattern.get('cta_placement', '')}")
470
+ if screen_pattern.get("conversion_optimization"):
471
+ lines.append(f"- **Conversion:** {screen_pattern.get('conversion_optimization', '')}")
472
+ lines.append("")
473
+
474
+ # Architecture section
475
+ lines.append("### Architecture")
476
+ lines.append(f"- **Pattern:** {pattern.get('name', '')}")
477
+ lines.append(f"- **Structure:** Feature-First / Clean Architecture")
478
+ lines.append(f"- **State Management:** {pattern.get('state_management', 'Riverpod')}")
479
+ if pattern.get('recommended_patterns'):
480
+ patterns_str = ", ".join(filter(None, pattern.get('recommended_patterns', [])))
481
+ lines.append(f"- **Recommended Patterns:** {patterns_str}")
482
+ lines.append("")
483
+
484
+ # Style section
485
+ lines.append("### UI Style")
486
+ lines.append(f"- **Name:** {style.get('name', '')}")
487
+ if style.get('keywords'):
488
+ lines.append(f"- **Keywords:** {style.get('keywords', '')}")
489
+ if style.get('best_for'):
490
+ lines.append(f"- **Best For:** {style.get('best_for', '')}")
491
+ if style.get('do_not_use'):
492
+ lines.append(f"- **Do Not Use For:** {style.get('do_not_use', '')}")
493
+ lines.append("")
494
+
495
+ # Colors section
496
+ lines.append("### Color Palette")
497
+ lines.append(f"| Role | Hex | Flutter |")
498
+ lines.append(f"|------|-----|---------|")
499
+ lines.append(f"| Primary | `{colors.get('primary', '')}` | `Color(0xFF{colors.get('primary', '#2563EB')[1:]})` |")
500
+ lines.append(f"| Secondary | `{colors.get('secondary', '')}` | `Color(0xFF{colors.get('secondary', '#3B82F6')[1:]})` |")
501
+ lines.append(f"| CTA | `{colors.get('cta', '')}` | `Color(0xFF{colors.get('cta', '#F97316')[1:]})` |")
502
+ lines.append(f"| Background | `{colors.get('background', '')}` | `Color(0xFF{colors.get('background', '#FFFFFF')[1:]})` |")
503
+ lines.append(f"| Surface | `{colors.get('surface', '')}` | `Color(0xFF{colors.get('surface', '#F8FAFC')[1:]})` |")
504
+ lines.append(f"| Text | `{colors.get('text', '')}` | `Color(0xFF{colors.get('text', '#1E293B')[1:]})` |")
505
+ if colors.get("notes"):
506
+ lines.append(f"\n*Notes: {colors.get('notes', '')}*")
507
+ if colors.get("strategy"):
508
+ lines.append(f"\n*Color Strategy: {colors.get('strategy', '')}*")
509
+ lines.append("")
510
+
511
+ # Typography section
512
+ lines.append("### Typography")
513
+ lines.append(f"- **Heading:** {typography.get('heading', '')}")
514
+ lines.append(f"- **Body:** {typography.get('body', '')}")
515
+ if typography.get("mood"):
516
+ lines.append(f"- **Mood:** {typography.get('mood', '')}")
517
+ if typography.get("best_for"):
518
+ lines.append(f"- **Best For:** {typography.get('best_for', '')}")
519
+ if typography.get("google_fonts_url"):
520
+ lines.append(f"- **Google Fonts:** {typography.get('google_fonts_url', '')}")
521
+ lines.append("")
522
+
523
+ # Key Effects section
524
+ if effects:
525
+ lines.append("### Key Effects")
526
+ lines.append(f"{effects}")
527
+ lines.append("")
528
+
529
+ # NEW: Conversion Focus section
530
+ if conversion_focus:
531
+ lines.append("### Conversion Focus")
532
+ lines.append(f"{conversion_focus}")
533
+ lines.append("")
534
+
535
+ # NEW: Must-Have Features section
536
+ if must_have_features:
537
+ lines.append("### Must-Have Features")
538
+ for feature in must_have_features:
539
+ lines.append(f"- ✓ {feature}")
540
+ lines.append("")
541
+
542
+ # Anti-patterns section
543
+ if anti_patterns:
544
+ lines.append("### Avoid (Anti-patterns)")
545
+ newline_bullet = '\n- '
546
+ lines.append(f"- {anti_patterns.replace(' + ', newline_bullet)}")
547
+ lines.append("")
548
+
549
+ # Pre-Delivery Checklist section
550
+ lines.append("### Pre-Delivery Checklist")
551
+ lines.append("- [ ] `const` constructors for immutable widgets")
552
+ lines.append("- [ ] Keys for lists and animated widgets")
553
+ lines.append("- [ ] Proper widget decomposition (no God widgets)")
554
+ lines.append("- [ ] Responsive: 375px, 768px, 1024px breakpoints")
555
+ lines.append("- [ ] Accessibility: Semantics labels, touch targets >= 48px")
556
+ lines.append("- [ ] Performance: ListView.builder for long lists")
557
+ lines.append("- [ ] Error handling: proper error/loading states")
558
+ lines.append("")
559
+
560
+ return "\n".join(lines)
561
+
562
+
563
+ # ============ PERSISTENCE FUNCTIONS ============
564
+ def persist_design_system(design_system: dict[str, Any], page: str | None = None, output_dir: str | None = None, page_query: str | None = None) -> dict[str, Any]:
565
+ """
566
+ Persist design system to design-system/<project>/ folder using Master + Overrides pattern.
567
+ """
568
+ base_dir = Path(output_dir) if output_dir else Path.cwd()
569
+
570
+ # Use project name for project-specific folder
571
+ project_name = design_system.get("project_name", "default")
572
+ project_slug = project_name.lower().replace(' ', '-')
573
+
574
+ design_system_dir = base_dir / "design-system" / project_slug
575
+ pages_dir = design_system_dir / "pages"
576
+
577
+ created_files: list[str] = []
578
+
579
+ # Create directories
580
+ design_system_dir.mkdir(parents=True, exist_ok=True)
581
+ pages_dir.mkdir(parents=True, exist_ok=True)
582
+
583
+ master_file = design_system_dir / "MASTER.md"
584
+
585
+ # Generate and write MASTER.md
586
+ master_content = format_master_md(design_system)
587
+ with open(master_file, 'w', encoding='utf-8') as f:
588
+ f.write(master_content)
589
+ created_files.append(str(master_file))
590
+
591
+ # If page is specified, create page override file
592
+ if page:
593
+ page_file = pages_dir / f"{page.lower().replace(' ', '-')}.md"
594
+ page_content = format_page_override_md(design_system, page, page_query)
595
+ with open(page_file, 'w', encoding='utf-8') as f:
596
+ f.write(page_content)
597
+ created_files.append(str(page_file))
598
+
599
+ return {
600
+ "status": "success",
601
+ "design_system_dir": str(design_system_dir),
602
+ "created_files": created_files
603
+ }
604
+
605
+
606
+ def format_master_md(design_system: dict[str, Any]) -> str:
607
+ """Format design system as MASTER.md with hierarchical override logic."""
608
+ project = design_system.get("project_name", "PROJECT")
609
+ pattern = design_system.get("pattern", {})
610
+ style = design_system.get("style", {})
611
+ colors = design_system.get("colors", {})
612
+ typography = design_system.get("typography", {})
613
+ effects = design_system.get("key_effects", "")
614
+ anti_patterns = design_system.get("anti_patterns", "")
615
+
616
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
617
+
618
+ lines: list[str] = []
619
+
620
+ # Logic header
621
+ lines.append("# Flutter Design System Master File")
622
+ lines.append("")
623
+ lines.append("> **LOGIC:** When building a specific screen, first check `design-system/pages/[screen-name].md`.")
624
+ lines.append("> If that file exists, its rules **override** this Master file.")
625
+ lines.append("> If not, strictly follow the rules below.")
626
+ lines.append("")
627
+ lines.append("---")
628
+ lines.append("")
629
+ lines.append(f"**Project:** {project}")
630
+ lines.append(f"**Generated:** {timestamp}")
631
+ lines.append(f"**Category:** {design_system.get('category', 'General')}")
632
+ lines.append("")
633
+ lines.append("---")
634
+ lines.append("")
635
+
636
+ # Architecture section
637
+ lines.append("## Architecture")
638
+ lines.append("")
639
+ lines.append(f"- **Pattern:** {pattern.get('name', 'Clean Architecture')}")
640
+ lines.append(f"- **Structure:** Feature-First")
641
+ lines.append(f"- **State Management:** {pattern.get('state_management', 'Riverpod')}")
642
+ lines.append("")
643
+ lines.append("### Folder Structure")
644
+ lines.append("```")
645
+ lines.append("lib/")
646
+ lines.append("├── core/ # Shared utilities, constants, themes")
647
+ lines.append("│ ├── theme/")
648
+ lines.append("│ ├── utils/")
649
+ lines.append("│ └── widgets/ # Reusable widgets")
650
+ lines.append("├── features/")
651
+ lines.append("│ └── [feature_name]/")
652
+ lines.append("│ ├── data/ # Repositories, data sources")
653
+ lines.append("│ ├── domain/ # Entities, use cases")
654
+ lines.append("│ └── presentation/ # Screens, widgets, state")
655
+ lines.append("└── main.dart")
656
+ lines.append("```")
657
+ lines.append("")
658
+
659
+ # Global Rules section
660
+ lines.append("## Global Rules")
661
+ lines.append("")
662
+
663
+ # Color Palette
664
+ lines.append("### Color Palette")
665
+ lines.append("")
666
+ lines.append("| Role | Hex | Flutter Color |")
667
+ lines.append("|------|-----|---------------|")
668
+ primary_hex = colors.get('primary', '#2563EB').lstrip('#')
669
+ secondary_hex = colors.get('secondary', '#3B82F6').lstrip('#')
670
+ cta_hex = colors.get('cta', '#F97316').lstrip('#')
671
+ bg_hex = colors.get('background', '#FFFFFF').lstrip('#')
672
+ surface_hex = colors.get('surface', '#F8FAFC').lstrip('#')
673
+ text_hex = colors.get('text', '#1E293B').lstrip('#')
674
+
675
+ lines.append(f"| Primary | `#{primary_hex}` | `Color(0xFF{primary_hex.upper()})` |")
676
+ lines.append(f"| Secondary | `#{secondary_hex}` | `Color(0xFF{secondary_hex.upper()})` |")
677
+ lines.append(f"| CTA/Accent | `#{cta_hex}` | `Color(0xFF{cta_hex.upper()})` |")
678
+ lines.append(f"| Background | `#{bg_hex}` | `Color(0xFF{bg_hex.upper()})` |")
679
+ lines.append(f"| Surface | `#{surface_hex}` | `Color(0xFF{surface_hex.upper()})` |")
680
+ lines.append(f"| Text | `#{text_hex}` | `Color(0xFF{text_hex.upper()})` |")
681
+ lines.append("")
682
+
683
+ if colors.get("notes"):
684
+ lines.append(f"**Color Notes:** {colors.get('notes', '')}")
685
+ lines.append("")
686
+
687
+ # Typography
688
+ lines.append("### Typography")
689
+ lines.append("")
690
+ lines.append(f"- **Heading Font:** {typography.get('heading', 'Inter')}")
691
+ lines.append(f"- **Body Font:** {typography.get('body', 'Inter')}")
692
+ if typography.get("mood"):
693
+ lines.append(f"- **Mood:** {typography.get('mood', '')}")
694
+ if typography.get("google_fonts_url"):
695
+ lines.append(f"- **Google Fonts:** [{typography.get('heading', '')} + {typography.get('body', '')}]({typography.get('google_fonts_url', '')})")
696
+ lines.append("")
697
+
698
+ lines.append("**Font Sizes (Scale):**")
699
+ lines.append("```dart")
700
+ lines.append("// Display")
701
+ lines.append("displayLarge: 57, displayMedium: 45, displaySmall: 36")
702
+ lines.append("// Headline")
703
+ lines.append("headlineLarge: 32, headlineMedium: 28, headlineSmall: 24")
704
+ lines.append("// Title")
705
+ lines.append("titleLarge: 22, titleMedium: 16, titleSmall: 14")
706
+ lines.append("// Body")
707
+ lines.append("bodyLarge: 16, bodyMedium: 14, bodySmall: 12")
708
+ lines.append("// Label")
709
+ lines.append("labelLarge: 14, labelMedium: 12, labelSmall: 11")
710
+ lines.append("```")
711
+ lines.append("")
712
+
713
+ # Spacing
714
+ lines.append("### Spacing")
715
+ lines.append("")
716
+ lines.append("| Token | Value | Usage |")
717
+ lines.append("|-------|-------|-------|")
718
+ lines.append("| `xs` | `4` | Tight gaps |")
719
+ lines.append("| `sm` | `8` | Icon gaps, inline spacing |")
720
+ lines.append("| `md` | `16` | Standard padding |")
721
+ lines.append("| `lg` | `24` | Section padding |")
722
+ lines.append("| `xl` | `32` | Large gaps |")
723
+ lines.append("| `xxl` | `48` | Section margins |")
724
+ lines.append("")
725
+
726
+ # Border Radius
727
+ lines.append("### Border Radius")
728
+ lines.append("")
729
+ lines.append("| Token | Value | Usage |")
730
+ lines.append("|-------|-------|-------|")
731
+ lines.append("| `xs` | `4` | Small chips, badges |")
732
+ lines.append("| `sm` | `8` | Buttons, inputs |")
733
+ lines.append("| `md` | `12` | Cards |")
734
+ lines.append("| `lg` | `16` | Modals, bottom sheets |")
735
+ lines.append("| `xl` | `24` | Large containers |")
736
+ lines.append("| `full` | `9999` | Pills, circular |")
737
+ lines.append("")
738
+
739
+ # Style section
740
+ lines.append("---")
741
+ lines.append("")
742
+ lines.append("## Style Guidelines")
743
+ lines.append("")
744
+ lines.append(f"**Style:** {style.get('name', 'Minimalism')}")
745
+ lines.append("")
746
+ if style.get("keywords"):
747
+ lines.append(f"**Keywords:** {style.get('keywords', '')}")
748
+ lines.append("")
749
+ if style.get("best_for"):
750
+ lines.append(f"**Best For:** {style.get('best_for', '')}")
751
+ lines.append("")
752
+ if effects:
753
+ lines.append(f"**Key Effects:** {effects}")
754
+ lines.append("")
755
+
756
+ # Anti-Patterns section
757
+ lines.append("---")
758
+ lines.append("")
759
+ lines.append("## Anti-Patterns (Do NOT Use)")
760
+ lines.append("")
761
+ if anti_patterns:
762
+ anti_list = [a.strip() for a in anti_patterns.split("+")]
763
+ for anti in anti_list:
764
+ if anti:
765
+ lines.append(f"- ❌ {anti}")
766
+ lines.append("")
767
+ lines.append("### Flutter-Specific Forbidden Patterns")
768
+ lines.append("")
769
+ lines.append("- ❌ **God Widgets** — Widgets > 200 lines, split into smaller components")
770
+ lines.append("- ❌ **Business logic in UI** — Move to providers/blocs/use cases")
771
+ lines.append("- ❌ **Missing const** — Always use `const` for immutable widgets")
772
+ lines.append("- ❌ **setState abuse** — Use proper state management for complex state")
773
+ lines.append("- ❌ **Hardcoded strings/colors** — Use theme and localization")
774
+ lines.append("- ❌ **Missing error states** — Always handle loading/error/empty states")
775
+ lines.append("")
776
+
777
+ # Pre-Delivery Checklist
778
+ lines.append("---")
779
+ lines.append("")
780
+ lines.append("## Pre-Delivery Checklist")
781
+ lines.append("")
782
+ lines.append("Before delivering any Flutter code, verify:")
783
+ lines.append("")
784
+ lines.append("- [ ] `const` constructors used for immutable widgets")
785
+ lines.append("- [ ] Proper Keys for lists and animated widgets")
786
+ lines.append("- [ ] Widget decomposition (no files > 300 lines)")
787
+ lines.append("- [ ] Responsive: works on 375px, 768px, 1024px")
788
+ lines.append("- [ ] Accessibility: Semantics widgets, touch targets >= 48x48")
789
+ lines.append("- [ ] Performance: ListView.builder for long lists")
790
+ lines.append("- [ ] State management: no setState for complex state")
791
+ lines.append("- [ ] Theme: colors and text styles from ThemeData")
792
+ lines.append("- [ ] Error handling: loading, error, empty states")
793
+ lines.append("- [ ] Null safety: no force unwrapping (!)")
794
+ lines.append("")
795
+
796
+ return "\n".join(lines)
797
+
798
+
799
+ def format_page_override_md(design_system: dict[str, Any], page_name: str, page_query: str | None = None) -> str:
800
+ """Format a page-specific override file."""
801
+ project = design_system.get("project_name", "PROJECT")
802
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
803
+ page_title = page_name.replace("-", " ").replace("_", " ").title()
804
+
805
+ # Detect page type and generate intelligent overrides
806
+ page_overrides = _generate_intelligent_overrides(page_name, page_query, design_system)
807
+
808
+ lines: list[str] = []
809
+
810
+ lines.append(f"# {page_title} Screen Overrides")
811
+ lines.append("")
812
+ lines.append(f"> **PROJECT:** {project}")
813
+ lines.append(f"> **Generated:** {timestamp}")
814
+ lines.append(f"> **Screen Type:** {page_overrides.get('page_type', 'General')}")
815
+ lines.append("")
816
+ lines.append("> ⚠️ **IMPORTANT:** Rules in this file **override** the Master file (`design-system/MASTER.md`).")
817
+ lines.append("> Only deviations from the Master are documented here. For all other rules, refer to the Master.")
818
+ lines.append("")
819
+ lines.append("---")
820
+ lines.append("")
821
+
822
+ # Screen-specific rules
823
+ lines.append("## Screen-Specific Rules")
824
+ lines.append("")
825
+
826
+ # Layout Overrides
827
+ lines.append("### Layout Overrides")
828
+ lines.append("")
829
+ layout = page_overrides.get("layout", {})
830
+ if layout:
831
+ for key, value in layout.items():
832
+ lines.append(f"- **{key}:** {value}")
833
+ else:
834
+ lines.append("- No overrides — use Master layout")
835
+ lines.append("")
836
+
837
+ # Widget Recommendations
838
+ lines.append("### Recommended Widgets")
839
+ lines.append("")
840
+ widgets = page_overrides.get("widgets", [])
841
+ if widgets:
842
+ for widget in widgets:
843
+ lines.append(f"- {widget}")
844
+ else:
845
+ lines.append("- Use standard widgets from Master")
846
+ lines.append("")
847
+
848
+ # State Management
849
+ lines.append("### State Management")
850
+ lines.append("")
851
+ state = page_overrides.get("state", {})
852
+ if state:
853
+ for key, value in state.items():
854
+ lines.append(f"- **{key}:** {value}")
855
+ else:
856
+ lines.append("- No overrides — use Master state management")
857
+ lines.append("")
858
+
859
+ # Recommendations
860
+ lines.append("---")
861
+ lines.append("")
862
+ lines.append("## Recommendations")
863
+ lines.append("")
864
+ recommendations = page_overrides.get("recommendations", [])
865
+ if recommendations:
866
+ for rec in recommendations:
867
+ lines.append(f"- {rec}")
868
+ lines.append("")
869
+
870
+ return "\n".join(lines)
871
+
872
+
873
+ def _generate_intelligent_overrides(page_name: str, page_query: str | None, design_system: dict[str, Any]) -> dict[str, Any]:
874
+ """
875
+ Generate intelligent overrides based on page type using layered search.
876
+ Enhanced with UX guidelines and landing pattern intelligence from UI-UX Pro Max.
877
+ """
878
+ from core import search
879
+
880
+ page_lower = page_name.lower()
881
+ query_lower = (page_query or "").lower()
882
+ combined_context = f"{page_lower} {query_lower}"
883
+
884
+ # Multi-domain search for page-specific guidance
885
+ pattern_search = search(combined_context, "pattern", max_results=2)
886
+ widget_search = search(combined_context, "widget", max_results=3)
887
+ ux_search = search(combined_context, "ux", max_results=3)
888
+ landing_search = search(combined_context, "landing", max_results=1)
889
+
890
+ pattern_results = pattern_search.get("results", [])
891
+ widget_results = widget_search.get("results", [])
892
+ ux_results = ux_search.get("results", [])
893
+ landing_results = landing_search.get("results", [])
894
+
895
+ # Detect page type
896
+ page_type = _detect_page_type(combined_context)
897
+
898
+ # Build overrides
899
+ layout: dict[str, str] = {}
900
+ widgets: list[str] = []
901
+ state: dict[str, str] = {}
902
+ recommendations: list[str] = []
903
+ ux_guidelines: list[str] = []
904
+
905
+ # Extract widget recommendations
906
+ for widget in widget_results:
907
+ widget_name = widget.get("Widget Name", "")
908
+ if widget_name:
909
+ widgets.append(f"`{widget_name}` - {widget.get('Description', '')[:60]}...")
910
+
911
+ # Extract pattern recommendations
912
+ for pattern in pattern_results:
913
+ pattern_name = pattern.get("pattern_name", "")
914
+ if pattern_name:
915
+ recommendations.append(f"Consider `{pattern_name}` pattern")
916
+
917
+ # NEW: Extract UX guidelines as recommendations
918
+ for ux in ux_results:
919
+ category = ux.get("Category", "")
920
+ do_text = ux.get("Do", "")
921
+ dont_text = ux.get("Don't", "")
922
+ if do_text:
923
+ ux_guidelines.append(f"✓ {category}: {do_text[:80]}...")
924
+ if dont_text:
925
+ ux_guidelines.append(f"✗ Avoid: {dont_text[:80]}...")
926
+
927
+ # NEW: Extract landing pattern info for screen structure
928
+ if landing_results:
929
+ landing = landing_results[0]
930
+ sections = landing.get("Section Order", "")
931
+ cta_placement = landing.get("Primary CTA Placement", "")
932
+ conversion = landing.get("Conversion Optimization", "")
933
+
934
+ if sections:
935
+ layout["Sections"] = sections[:80]
936
+ if cta_placement:
937
+ layout["CTA Placement"] = cta_placement
938
+ if conversion:
939
+ recommendations.append(f"Conversion: {conversion[:80]}...")
940
+
941
+ # Add page-type specific defaults
942
+ if "dashboard" in page_lower or "analytics" in page_lower:
943
+ layout["Grid"] = "Use GridView or Wrap for widget cards"
944
+ layout["Scrolling"] = "CustomScrollView with slivers"
945
+ state["Refresh"] = "Pull-to-refresh with RefreshIndicator"
946
+ state["Real-time"] = "Consider StreamBuilder for live data"
947
+ elif "list" in page_lower or "search" in page_lower:
948
+ layout["List"] = "ListView.builder for performance"
949
+ layout["Pagination"] = "Infinite scroll with lazy loading"
950
+ state["Search"] = "Debounced search with 300ms delay"
951
+ state["Empty State"] = "Show helpful empty state with action"
952
+ elif "form" in page_lower or "profile" in page_lower or "settings" in page_lower:
953
+ layout["Form"] = "Single column, max 600px width"
954
+ state["Validation"] = "Form validation with FormField"
955
+ state["Autosave"] = "Consider debounced autosave for settings"
956
+ elif "login" in page_lower or "auth" in page_lower:
957
+ layout["Layout"] = "Centered, max 400px width"
958
+ state["Auth"] = "Handle loading, error, success states"
959
+ state["Biometric"] = "Consider biometric authentication"
960
+ elif "checkout" in page_lower or "payment" in page_lower:
961
+ layout["Layout"] = "Single column, focused flow"
962
+ state["Progress"] = "Show step indicator for multi-step"
963
+ state["Validation"] = "Real-time validation for payment fields"
964
+ elif "chat" in page_lower or "message" in page_lower:
965
+ layout["List"] = "Reversed ListView for chat bubbles"
966
+ state["Real-time"] = "WebSocket or Firebase for live updates"
967
+ state["Typing"] = "Show typing indicator"
968
+
969
+ # Add UX guidelines to recommendations
970
+ recommendations.extend(ux_guidelines[:3]) # Limit to top 3
971
+
972
+ if not recommendations:
973
+ recommendations = [
974
+ "Refer to MASTER.md for all design rules",
975
+ "Add specific overrides as needed for this screen"
976
+ ]
977
+
978
+ return {
979
+ "page_type": page_type,
980
+ "layout": layout,
981
+ "widgets": widgets,
982
+ "state": state,
983
+ "recommendations": recommendations
984
+ }
985
+
986
+
987
+ def _detect_page_type(context: str) -> str:
988
+ """Detect page type from context."""
989
+ context_lower = context.lower()
990
+
991
+ page_patterns: list[tuple[list[str], str]] = [
992
+ (["dashboard", "admin", "analytics", "metrics", "stats", "overview"], "Dashboard"),
993
+ (["list", "search", "browse", "filter", "catalog"], "List / Search"),
994
+ (["detail", "product", "item", "view"], "Detail View"),
995
+ (["form", "edit", "create", "add", "new"], "Form / Input"),
996
+ (["profile", "settings", "account", "preferences"], "Settings / Profile"),
997
+ (["login", "signin", "signup", "register", "auth"], "Authentication"),
998
+ (["home", "landing", "welcome"], "Home / Landing"),
999
+ (["chat", "message", "conversation"], "Chat / Messaging"),
1000
+ (["checkout", "payment", "cart", "order"], "Checkout / Payment"),
1001
+ ]
1002
+
1003
+ for keywords, page_type in page_patterns:
1004
+ if any(kw in context_lower for kw in keywords):
1005
+ return page_type
1006
+
1007
+ return "General"
1008
+
1009
+
1010
+ # ============ MAIN ENTRY POINT ============
1011
+ def generate_design_system(query: str, project_name: str | None = None, output_format: str = "ascii",
1012
+ persist: bool = False, page: str | None = None, output_dir: str | None = None) -> str:
1013
+ """
1014
+ Main entry point for design system generation.
1015
+
1016
+ Args:
1017
+ query: Search query (e.g., "fintech banking app", "e-commerce mobile")
1018
+ project_name: Optional project name for output header
1019
+ output_format: "ascii" (default) or "markdown"
1020
+ persist: If True, save design system to design-system/ folder
1021
+ page: Optional page name for page-specific override file
1022
+ output_dir: Optional output directory (defaults to current working directory)
1023
+
1024
+ Returns:
1025
+ Formatted design system string
1026
+ """
1027
+ generator = DesignSystemGenerator()
1028
+ design_system = generator.generate(query, project_name)
1029
+
1030
+ # Persist to files if requested
1031
+ if persist:
1032
+ persist_design_system(design_system, page, output_dir, query)
1033
+
1034
+ if output_format == "markdown":
1035
+ return format_markdown(design_system)
1036
+ return format_ascii_box(design_system)
1037
+
1038
+
1039
+ # ============ CLI SUPPORT ============
1040
+ if __name__ == "__main__":
1041
+ import argparse
1042
+
1043
+ parser = argparse.ArgumentParser(description="Generate Flutter Design System")
1044
+ parser.add_argument("query", help="Search query (e.g., 'fintech banking app')")
1045
+ parser.add_argument("--project-name", "-p", type=str, default=None, help="Project name")
1046
+ parser.add_argument("--format", "-f", choices=["ascii", "markdown"], default="ascii", help="Output format")
1047
+ parser.add_argument("--persist", action="store_true", help="Save to design-system/ folder")
1048
+ parser.add_argument("--page", type=str, default=None, help="Create page-specific override file")
1049
+ parser.add_argument("--output-dir", "-o", type=str, default=None, help="Output directory")
1050
+
1051
+ args = parser.parse_args()
1052
+
1053
+ result = generate_design_system(
1054
+ args.query,
1055
+ args.project_name,
1056
+ args.format,
1057
+ persist=args.persist,
1058
+ page=args.page,
1059
+ output_dir=args.output_dir
1060
+ )
1061
+ print(result)
1062
+
1063
+ if args.persist:
1064
+ project_slug = args.project_name.lower().replace(' ', '-') if args.project_name else "default"
1065
+ print("\n" + "=" * 60)
1066
+ print(f"✅ Design system persisted to design-system/{project_slug}/")
1067
+ print(f" 📄 design-system/{project_slug}/MASTER.md (Global Source of Truth)")
1068
+ if args.page:
1069
+ page_filename = args.page.lower().replace(' ', '-')
1070
+ print(f" 📄 design-system/{project_slug}/pages/{page_filename}.md (Screen Overrides)")
1071
+ print("")
1072
+ print(f"📖 Usage: When building a screen, check design-system/{project_slug}/pages/[screen].md first.")
1073
+ print(f" If exists, its rules override MASTER.md. Otherwise, use MASTER.md.")
1074
+ print("=" * 60)