flutter-pro-max-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/assets/.agent/workflows/flutter-pro-max.md +167 -0
  4. package/assets/.agent/workflows/scripts/core.py +341 -0
  5. package/assets/.agent/workflows/scripts/search.py +106 -0
  6. package/assets/.claude/skills/flutter-pro-max/SKILL.md +182 -0
  7. package/assets/.claude/skills/flutter-pro-max/scripts/core.py +341 -0
  8. package/assets/.claude/skills/flutter-pro-max/scripts/search.py +106 -0
  9. package/assets/.codebuddy/commands/flutter-pro-max.md +58 -0
  10. package/assets/.codebuddy/commands/scripts/core.py +341 -0
  11. package/assets/.codebuddy/commands/scripts/search.py +106 -0
  12. package/assets/.codex/skills/flutter-pro-max/SKILL.md +100 -0
  13. package/assets/.codex/skills/flutter-pro-max/scripts/core.py +341 -0
  14. package/assets/.codex/skills/flutter-pro-max/scripts/search.py +106 -0
  15. package/assets/.cursor/commands/flutter-pro-max.md +154 -0
  16. package/assets/.cursor/commands/scripts/core.py +341 -0
  17. package/assets/.cursor/commands/scripts/search.py +106 -0
  18. package/assets/.gemini/skills/flutter-pro-max/SKILL.md +100 -0
  19. package/assets/.gemini/skills/flutter-pro-max/scripts/core.py +341 -0
  20. package/assets/.gemini/skills/flutter-pro-max/scripts/search.py +106 -0
  21. package/assets/.github/prompts/flutter-pro-max.prompt.md +61 -0
  22. package/assets/.github/prompts/scripts/core.py +341 -0
  23. package/assets/.github/prompts/scripts/search.py +106 -0
  24. package/assets/.kiro/steering/flutter-pro-max.md +60 -0
  25. package/assets/.kiro/steering/scripts/core.py +341 -0
  26. package/assets/.kiro/steering/scripts/search.py +106 -0
  27. package/assets/.qoder/rules/flutter-pro-max.md +58 -0
  28. package/assets/.qoder/rules/scripts/core.py +341 -0
  29. package/assets/.qoder/rules/scripts/search.py +106 -0
  30. package/assets/.roo/commands/flutter-pro-max.md +58 -0
  31. package/assets/.roo/commands/scripts/core.py +341 -0
  32. package/assets/.roo/commands/scripts/search.py +106 -0
  33. package/assets/.shared/data/architect.csv +18 -0
  34. package/assets/.shared/data/charts.csv +26 -0
  35. package/assets/.shared/data/colors.csv +97 -0
  36. package/assets/.shared/data/icons.csv +101 -0
  37. package/assets/.shared/data/landing.csv +31 -0
  38. package/assets/.shared/data/name_convention.csv +16 -0
  39. package/assets/.shared/data/package.csv +101 -0
  40. package/assets/.shared/data/patterns.csv +109 -0
  41. package/assets/.shared/data/products.csv +97 -0
  42. package/assets/.shared/data/prompts.csv +24 -0
  43. package/assets/.shared/data/styles.csv +59 -0
  44. package/assets/.shared/data/typography.csv +58 -0
  45. package/assets/.shared/data/ux-guidelines.csv +100 -0
  46. package/assets/.shared/data/widget.csv +65 -0
  47. package/assets/.shared/flutter-pro-max/SKILL.md +165 -0
  48. package/assets/.shared/flutter-pro-max/scripts/core.py +341 -0
  49. package/assets/.shared/flutter-pro-max/scripts/search.py +106 -0
  50. package/assets/.trae/skills/flutter-pro-max/SKILL.md +61 -0
  51. package/assets/.trae/skills/flutter-pro-max/scripts/core.py +341 -0
  52. package/assets/.trae/skills/flutter-pro-max/scripts/search.py +106 -0
  53. package/assets/.windsurf/workflows/flutter-pro-max.md +154 -0
  54. package/assets/.windsurf/workflows/scripts/core.py +341 -0
  55. package/assets/.windsurf/workflows/scripts/search.py +106 -0
  56. package/dist/commands/init.d.ts +7 -0
  57. package/dist/commands/init.js +67 -0
  58. package/dist/index.d.ts +2 -0
  59. package/dist/index.js +31 -0
  60. package/dist/types/index.d.ts +20 -0
  61. package/dist/types/index.js +15 -0
  62. package/dist/utils/detect.d.ts +8 -0
  63. package/dist/utils/detect.js +80 -0
  64. package/dist/utils/extract.d.ts +4 -0
  65. package/dist/utils/extract.js +83 -0
  66. package/dist/utils/logger.d.ts +8 -0
  67. package/dist/utils/logger.js +9 -0
  68. package/package.json +52 -0
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Flutter Pro Max Search - CLI for Flutter knowledge base search
5
+ Usage: python search.py "<query>" [--domain <domain>] [--stack <stack>] [--top 5]
6
+
7
+ Domains: widget, package, pattern, architect, chart, color, typography, style, ux, icon, landing, naming, product, prompt
8
+ Stacks: riverpod, bloc, provider
9
+ """
10
+
11
+ import argparse
12
+ import json
13
+ from core import (
14
+ CSV_CONFIG,
15
+ AVAILABLE_DOMAINS,
16
+ AVAILABLE_STACKS,
17
+ MAX_RESULTS,
18
+ search,
19
+ search_with_stack
20
+ )
21
+
22
+
23
+ def format_output(result):
24
+ """Format results for human-readable output (token-optimized for AI)"""
25
+ if "error" in result:
26
+ return f"❌ Error: {result['error']}"
27
+
28
+ output = []
29
+
30
+ # Header
31
+ output.append(f"\n🔍 Flutter Pro Max Search Results")
32
+ if result.get("stack"):
33
+ output.append(f" Stack: {result['stack']} (excluding: {', '.join(result.get('excluded', []))})")
34
+ output.append(f" Domain: {result['domain']} | Query: '{result['query']}'")
35
+ output.append(f" Source: {result['file']} | Found: {result['count']} results\n")
36
+
37
+ # Results
38
+ for i, row in enumerate(result['results'], 1):
39
+ output.append(f"{'='*60}")
40
+ output.append(f"[{i}] Score: {row.get('_score', 'N/A')}")
41
+
42
+ for key, value in row.items():
43
+ if key == "_score":
44
+ continue
45
+ value_str = str(value)
46
+ if len(value_str) > 200:
47
+ value_str = value_str[:200] + "..."
48
+ output.append(f" {key}: {value_str}")
49
+ output.append("")
50
+
51
+ return "\n".join(output)
52
+
53
+
54
+ def main():
55
+ parser = argparse.ArgumentParser(
56
+ description="Flutter Pro Max Search - BM25 search for Flutter knowledge base",
57
+ formatter_class=argparse.RawDescriptionHelpFormatter,
58
+ epilog="""
59
+ Examples:
60
+ python search.py "ListView pagination" --top 3
61
+ python search.py "network http" --domain package
62
+ python search.py "state management" --stack riverpod
63
+ python search.py "login" --json
64
+ """
65
+ )
66
+
67
+ parser.add_argument("query", help="Search query")
68
+ parser.add_argument(
69
+ "--domain", "-d",
70
+ choices=AVAILABLE_DOMAINS,
71
+ help=f"Search domain (auto-detected if not specified)"
72
+ )
73
+ parser.add_argument(
74
+ "--stack", "-s",
75
+ choices=AVAILABLE_STACKS,
76
+ help="Stack preference to exclude conflicting packages"
77
+ )
78
+ parser.add_argument(
79
+ "--top", "-n",
80
+ type=int,
81
+ default=MAX_RESULTS,
82
+ help=f"Max results (default: {MAX_RESULTS})"
83
+ )
84
+ parser.add_argument(
85
+ "--json", "-j",
86
+ action="store_true",
87
+ help="Output as JSON"
88
+ )
89
+
90
+ args = parser.parse_args()
91
+
92
+ # Perform search
93
+ if args.stack:
94
+ result = search_with_stack(args.query, args.stack, args.domain, args.top)
95
+ else:
96
+ result = search(args.query, args.domain, args.top)
97
+
98
+ # Output
99
+ if args.json:
100
+ print(json.dumps(result, indent=2, ensure_ascii=False))
101
+ else:
102
+ print(format_output(result))
103
+
104
+
105
+ if __name__ == "__main__":
106
+ main()
@@ -0,0 +1,182 @@
1
+ ---
2
+ name: flutter-pro-max
3
+ description: Chuyên gia Flutter với kiến thức sâu về Clean Architecture, Performance và Modern Dart 3
4
+ ---
5
+
6
+ # Flutter Pro Max - Flutter Design Intelligence
7
+
8
+ Searchable database của Flutter widgets, packages, design patterns, architecture guidelines, và best practices.
9
+
10
+ ## Prerequisites
11
+
12
+ Kiểm tra Python đã cài đặt:
13
+
14
+ ```bash
15
+ python3 --version || python --version
16
+ ```
17
+
18
+ Cài đặt dependency:
19
+
20
+ ```bash
21
+ pip install rank-bm25
22
+ ```
23
+
24
+ ---
25
+
26
+ ## How to Use This Skill
27
+
28
+ Khi user yêu cầu Flutter work (design, build, create, implement, review, fix, improve), follow workflow này:
29
+
30
+ ### Step 1: Analyze User Requirements
31
+
32
+ Trích xuất thông tin từ request:
33
+ - **Architecture**: Clean Architecture, Feature-First, DDD
34
+ - **State Management**: Riverpod (default), Bloc, Provider
35
+ - **UI Components**: Widgets, Layouts, Animations
36
+ - **Design**: Colors, Typography, Styles
37
+ - **Package needs**: Networking, Database, Security, etc.
38
+
39
+ ### Step 2: Search Relevant Data
40
+
41
+ Sử dụng `search.py` để tìm kiếm trong **14 data sources**:
42
+
43
+ ```bash
44
+ python3 .claude/skills/flutter-pro-max/scripts/search.py "<keyword>" --top 5
45
+ ```
46
+
47
+ **Với stack filter (loại bỏ conflicts):**
48
+ ```bash
49
+ python3 .claude/skills/flutter-pro-max/scripts/search.py "<keyword>" --stack riverpod --top 5
50
+ ```
51
+
52
+ **Available stacks:** `riverpod`, `bloc`, `provider`
53
+
54
+ **Search Examples by Domain:**
55
+ ```bash
56
+ # Flutter Widgets
57
+ python3 scripts/search.py "ListView pagination" --top 5
58
+
59
+ # Design Patterns
60
+ python3 scripts/search.py "authentication login" --top 5
61
+
62
+ # Charts
63
+ python3 scripts/search.py "chart bar comparison" --top 5
64
+
65
+ # Typography
66
+ python3 scripts/search.py "font modern SaaS" --top 5
67
+
68
+ # Colors by Product
69
+ python3 scripts/search.py "fintech crypto dark" --top 5
70
+
71
+ # UX Guidelines
72
+ python3 scripts/search.py "touch target accessibility" --top 5
73
+
74
+ # UI Styles
75
+ python3 scripts/search.py "glassmorphism neumorphism" --top 5
76
+ ```
77
+
78
+ ### Step 3: Apply Technical Standards
79
+
80
+ Luôn tuân thủ các tiêu chuẩn:
81
+
82
+ #### Dart 3 Modern Syntax
83
+ ```dart
84
+ // ✅ Records
85
+ (String name, int age) getUserInfo() => ('John', 25);
86
+
87
+ // ✅ Pattern Matching
88
+ String getMessage(UIState state) => switch (state) {
89
+ LoadingState() => 'Loading...',
90
+ DataState(data: var d) => 'Data: $d',
91
+ ErrorState(message: var m) => 'Error: $m',
92
+ };
93
+ ```
94
+
95
+ #### Performance Rules
96
+ - Luôn dùng `const` constructor khi có thể
97
+ - Ưu tiên `SizedBox` hơn `Container` cho spacing
98
+ - Dùng `ListView.builder` thay vì `ListView` + `children`
99
+
100
+ #### State Management
101
+ - **Default**: Riverpod với `riverpod_generator`
102
+ - **Alternative**: Bloc (khi user yêu cầu)
103
+
104
+ ---
105
+
106
+ ## Search Reference
107
+
108
+ ### Available Data Sources (14 files)
109
+
110
+ | Type | File | Content |
111
+ |------|------|---------|
112
+ | Widget | `widget.csv` | 65+ Flutter widgets với pro-tips |
113
+ | Package | `package.csv` | 100+ packages với best practices |
114
+ | Pattern | `patterns.csv` | 100+ design patterns với code snippets |
115
+ | Architecture | `architect.csv` | Clean Architecture layer paths |
116
+ | Chart | `charts.csv` | Chart type recommendations by data |
117
+ | Color | `colors.csv` | Color palettes by product type |
118
+ | Typography | `typography.csv` | Font pairings với Google Fonts |
119
+ | Style | `styles.csv` | UI style guidelines (Glass, Neubrutalism...) |
120
+ | UX Guideline | `ux-guidelines.csv` | UX best practices (Do/Don't) |
121
+ | Icon | `icons.csv` | Icon recommendations |
122
+ | Landing | `landing.csv` | Landing page section patterns |
123
+ | Naming | `name_convention.csv` | Dart/Flutter naming conventions |
124
+ | Product | `products.csv` | Product type styling recommendations |
125
+ | Prompt | `prompts.csv` | AI prompt templates |
126
+
127
+ ---
128
+
129
+ ## Example Workflow
130
+
131
+ **User Request:** "Tạo màn hình đăng nhập với Riverpod"
132
+
133
+ 1. **Search widgets:**
134
+ ```bash
135
+ python3 scripts/search.py "form input text field" --top 5
136
+ ```
137
+
138
+ 2. **Search patterns:**
139
+ ```bash
140
+ python3 scripts/search.py "authentication login" --top 5
141
+ ```
142
+
143
+ 3. **Search packages:**
144
+ ```bash
145
+ python3 scripts/search.py "validation form" --stack riverpod --top 5
146
+ ```
147
+
148
+ 4. **Search colors:**
149
+ ```bash
150
+ python3 scripts/search.py "saas professional" --top 3
151
+ ```
152
+
153
+ 5. **Apply results** to generate code với Riverpod state management
154
+
155
+ ---
156
+
157
+ ## Pre-Delivery Checklist
158
+
159
+ ### Code Quality
160
+ - [ ] Sử dụng `const` constructors
161
+ - [ ] Sound Null Safety (không dùng `!` bừa bãi)
162
+ - [ ] Dart 3 syntax (Records, Pattern Matching)
163
+
164
+ ### Performance
165
+ - [ ] `ListView.builder` cho lists dài
166
+ - [ ] `SizedBox` thay vì `Container` cho spacing
167
+ - [ ] `const` widgets được đánh dấu
168
+
169
+ ### Architecture
170
+ - [ ] Tuân thủ Clean Architecture layers
171
+ - [ ] Dependency Injection đúng cách
172
+ - [ ] Repository pattern cho data access
173
+
174
+ ### State Management
175
+ - [ ] Riverpod providers được tổ chức hợp lý
176
+ - [ ] Không leak state giữa các features
177
+ - [ ] Error handling với AsyncValue
178
+
179
+ ### UX/UI
180
+ - [ ] Touch targets tối thiểu 44x44px
181
+ - [ ] Colors đúng với product type
182
+ - [ ] Typography phù hợp với brand
@@ -0,0 +1,341 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Flutter Pro Max Core - BM25 search engine for Flutter knowledge base
5
+ Zero dependencies - self-contained BM25 implementation
6
+ """
7
+
8
+ import csv
9
+ import re
10
+ from pathlib import Path
11
+ from math import log
12
+ from collections import defaultdict
13
+
14
+ # ============ CONFIGURATION ============
15
+ def _get_data_dir():
16
+ """Auto-detect data directory based on script location"""
17
+ script_dir = Path(__file__).parent
18
+ possible_paths = [
19
+ # When running from root/scripts/
20
+ script_dir.parent / ".shared" / "data",
21
+ # When running from .shared/flutter-pro-max/scripts/
22
+ script_dir.parent.parent / "data",
23
+ # Fallback: cwd
24
+ Path.cwd() / ".shared" / "data",
25
+ ]
26
+ for p in possible_paths:
27
+ if p.exists():
28
+ return p.resolve()
29
+ return possible_paths[0] # Default to first option
30
+
31
+ DATA_DIR = _get_data_dir()
32
+ MAX_RESULTS = 5
33
+
34
+ # Domain configuration: file, search columns, output columns
35
+ CSV_CONFIG = {
36
+ "widget": {
37
+ "file": "widget.csv",
38
+ "search_cols": ["Widget Name", "Category", "Description", "Key Properties", "Usage Context & Pro-Tips"],
39
+ "output_cols": ["Widget Name", "Category", "Description", "Key Properties", "Usage Context & Pro-Tips"]
40
+ },
41
+ "package": {
42
+ "file": "package.csv",
43
+ "search_cols": ["pkg_name", "category", "description", "best_practice_snippet", "pro_tip", "alternatives"],
44
+ "output_cols": ["pkg_name", "category", "description", "best_practice_snippet", "pro_tip", "alternatives"]
45
+ },
46
+ "pattern": {
47
+ "file": "patterns.csv",
48
+ "search_cols": ["pattern_name", "category", "problem_tags", "description", "key_widgets", "code_snippet", "anti_pattern"],
49
+ "output_cols": ["pattern_name", "category", "problem_tags", "description", "key_widgets", "code_snippet", "anti_pattern"]
50
+ },
51
+ "architect": {
52
+ "file": "architect.csv",
53
+ "search_cols": ["path_pattern", "layer", "responsibility_description", "allowed_dependencies", "tech_stack_note"],
54
+ "output_cols": ["path_pattern", "layer", "responsibility_description", "allowed_dependencies", "tech_stack_note"]
55
+ },
56
+ "chart": {
57
+ "file": "charts.csv",
58
+ "search_cols": ["Data Type", "Keywords", "Best Chart Type", "Secondary Options", "Accessibility Notes"],
59
+ "output_cols": ["Data Type", "Keywords", "Best Chart Type", "Secondary Options", "Color Guidance", "Accessibility Notes", "Library Recommendation"]
60
+ },
61
+ "color": {
62
+ "file": "colors.csv",
63
+ "search_cols": ["Product Type", "Keywords", "Notes"],
64
+ "output_cols": ["Product Type", "Keywords", "Primary (Hex)", "Secondary (Hex)", "CTA (Hex)", "Notes"]
65
+ },
66
+ "typography": {
67
+ "file": "typography.csv",
68
+ "search_cols": ["Font Pairing Name", "Category", "Mood/Style Keywords", "Best For", "Heading Font", "Body Font"],
69
+ "output_cols": ["Font Pairing Name", "Category", "Heading Font", "Body Font", "Mood/Style Keywords", "Best For", "Google Fonts URL", "Notes"]
70
+ },
71
+ "style": {
72
+ "file": "styles.csv",
73
+ "search_cols": ["Style Category", "Type", "Keywords", "Best For"],
74
+ "output_cols": ["Style Category", "Type", "Keywords", "Primary Colors", "Effects & Animation", "Best For", "Do Not Use For"]
75
+ },
76
+ "ux": {
77
+ "file": "ux-guidelines.csv",
78
+ "search_cols": ["Category", "Issue", "Platform", "Description", "Do", "Don't"],
79
+ "output_cols": ["Category", "Issue", "Platform", "Description", "Do", "Don't"]
80
+ },
81
+ "icon": {
82
+ "file": "icons.csv",
83
+ "search_cols": ["Category", "Icon Name", "Keywords", "Best For"],
84
+ "output_cols": ["Category", "Icon Name", "Keywords", "Library", "Import Code", "Usage", "Best For"]
85
+ },
86
+ "landing": {
87
+ "file": "landing.csv",
88
+ "search_cols": ["Pattern Name", "Keywords", "Section Order", "Conversion Optimization"],
89
+ "output_cols": ["Pattern Name", "Keywords", "Section Order", "Primary CTA Placement", "Color Strategy", "Conversion Optimization"]
90
+ },
91
+ "naming": {
92
+ "file": "name_convention.csv",
93
+ "search_cols": ["Layer", "File Template", "Class Template", "Example File", "Example Class", "Notes"],
94
+ "output_cols": ["Layer", "File Template", "Class Template", "Example File", "Example Class", "Notes"]
95
+ },
96
+ "product": {
97
+ "file": "products.csv",
98
+ "search_cols": ["Product Type", "Keywords", "Primary Style Recommendation"],
99
+ "output_cols": ["Product Type", "Keywords", "Primary Style Recommendation", "Secondary Styles", "Color Palette Focus"]
100
+ },
101
+ "prompt": {
102
+ "file": "prompts.csv",
103
+ "search_cols": ["Style Category", "AI Prompt Keywords (Copy-Paste Ready)", "CSS/Technical Keywords"],
104
+ "output_cols": ["Style Category", "AI Prompt Keywords (Copy-Paste Ready)", "CSS/Technical Keywords", "Implementation Checklist"]
105
+ }
106
+ }
107
+
108
+ # Stack exclusion mapping for filtering conflicting packages
109
+ STACK_EXCLUSIONS = {
110
+ "riverpod": ["bloc", "flutter_bloc", "provider", "hydrated_bloc"],
111
+ "bloc": ["riverpod", "flutter_riverpod", "provider"],
112
+ "provider": ["riverpod", "flutter_riverpod", "bloc", "flutter_bloc"],
113
+ }
114
+
115
+ AVAILABLE_DOMAINS = list(CSV_CONFIG.keys())
116
+ AVAILABLE_STACKS = list(STACK_EXCLUSIONS.keys())
117
+
118
+
119
+ # ============ BM25 IMPLEMENTATION ============
120
+ class BM25:
121
+ """BM25 ranking algorithm for text search - zero dependencies"""
122
+
123
+ def __init__(self, k1=1.5, b=0.75):
124
+ self.k1 = k1
125
+ self.b = b
126
+ self.corpus = []
127
+ self.doc_lengths = []
128
+ self.avgdl = 0
129
+ self.idf = {}
130
+ self.doc_freqs = defaultdict(int)
131
+ self.N = 0
132
+
133
+ def tokenize(self, text):
134
+ """Lowercase, split, remove punctuation, filter short words"""
135
+ text = re.sub(r'[^\w\s]', ' ', str(text).lower())
136
+ return [w for w in text.split() if len(w) > 1]
137
+
138
+ def fit(self, documents):
139
+ """Build BM25 index from documents"""
140
+ self.corpus = [self.tokenize(doc) for doc in documents]
141
+ self.N = len(self.corpus)
142
+ if self.N == 0:
143
+ return
144
+ self.doc_lengths = [len(doc) for doc in self.corpus]
145
+ self.avgdl = sum(self.doc_lengths) / self.N
146
+
147
+ for doc in self.corpus:
148
+ seen = set()
149
+ for word in doc:
150
+ if word not in seen:
151
+ self.doc_freqs[word] += 1
152
+ seen.add(word)
153
+
154
+ for word, freq in self.doc_freqs.items():
155
+ self.idf[word] = log((self.N - freq + 0.5) / (freq + 0.5) + 1)
156
+
157
+ def score(self, query):
158
+ """Score all documents against query"""
159
+ query_tokens = self.tokenize(query)
160
+ scores = []
161
+
162
+ for idx, doc in enumerate(self.corpus):
163
+ score = 0
164
+ doc_len = self.doc_lengths[idx]
165
+ term_freqs = defaultdict(int)
166
+ for word in doc:
167
+ term_freqs[word] += 1
168
+
169
+ for token in query_tokens:
170
+ if token in self.idf:
171
+ tf = term_freqs[token]
172
+ idf = self.idf[token]
173
+ numerator = tf * (self.k1 + 1)
174
+ denominator = tf + self.k1 * (1 - self.b + self.b * doc_len / self.avgdl)
175
+ score += idf * numerator / denominator
176
+
177
+ scores.append((idx, score))
178
+
179
+ return sorted(scores, key=lambda x: x[1], reverse=True)
180
+
181
+
182
+ # ============ HELPER FUNCTIONS ============
183
+ def _load_csv(filepath):
184
+ """Load CSV and return list of dicts"""
185
+ with open(filepath, 'r', encoding='utf-8') as f:
186
+ return list(csv.DictReader(f))
187
+
188
+
189
+ def _search_csv(filepath, search_cols, output_cols, query, max_results, boost_col=None, boost_query=None):
190
+ """Core search function using BM25 with optional boosting"""
191
+ if not filepath.exists():
192
+ return []
193
+
194
+ data = _load_csv(filepath)
195
+
196
+ # Build documents from search columns
197
+ documents = [" ".join(str(row.get(col, "")) for col in search_cols) for row in data]
198
+
199
+ # BM25 search
200
+ bm25 = BM25()
201
+ bm25.fit(documents)
202
+ ranked = bm25.score(query)
203
+
204
+ # Apply boosting if specified (widget name match, etc.)
205
+ if boost_col and boost_query:
206
+ boost_query_lower = boost_query.lower()
207
+ boosted = []
208
+ for idx, score in ranked:
209
+ if score > 0:
210
+ boost_value = str(data[idx].get(boost_col, "")).lower()
211
+ if boost_value in boost_query_lower or boost_query_lower in boost_value:
212
+ score *= 2.0 # Double score for exact/partial match
213
+ boosted.append((idx, score))
214
+ ranked = sorted(boosted, key=lambda x: x[1], reverse=True)
215
+
216
+ # Get top results with score > 0
217
+ results = []
218
+ for idx, score in ranked[:max_results]:
219
+ if score > 0:
220
+ row = data[idx]
221
+ result = {col: row.get(col, "") for col in output_cols if col in row}
222
+ result["_score"] = round(score, 4)
223
+ results.append(result)
224
+
225
+ return results
226
+
227
+
228
+ def detect_domain(query):
229
+ """Auto-detect the most relevant domain from query keywords"""
230
+ query_lower = query.lower()
231
+
232
+ domain_keywords = {
233
+ "widget": ["widget", "listview", "column", "row", "container", "text", "button", "scaffold", "appbar", "sliver"],
234
+ "package": ["package", "pub", "dependency", "library", "dio", "http", "riverpod", "bloc", "hive", "isar"],
235
+ "pattern": ["pattern", "architecture", "repository", "usecase", "state", "async", "error handling", "offline"],
236
+ "architect": ["layer", "folder", "structure", "clean", "feature", "domain", "data", "presentation"],
237
+ "chart": ["chart", "graph", "visualization", "bar", "pie", "line", "scatter", "heatmap"],
238
+ "color": ["color", "palette", "hex", "theme", "dark mode", "light mode"],
239
+ "typography": ["font", "typography", "heading", "text style", "google fonts"],
240
+ "style": ["style", "design", "ui", "glassmorphism", "neumorphism", "minimal", "modern"],
241
+ "ux": ["ux", "usability", "accessibility", "touch", "scroll", "animation", "gesture"],
242
+ "icon": ["icon", "lucide", "material icons", "cupertino"],
243
+ "landing": ["landing", "page", "hero", "cta", "section"],
244
+ "naming": ["naming", "convention", "file name", "class name", "snake_case", "PascalCase"],
245
+ "product": ["saas", "ecommerce", "fintech", "healthcare", "education", "food", "travel"],
246
+ "prompt": ["prompt", "ai", "css", "tailwind", "implementation"],
247
+ }
248
+
249
+ scores = {domain: sum(1 for kw in keywords if kw in query_lower) for domain, keywords in domain_keywords.items()}
250
+ best = max(scores, key=scores.get)
251
+ return best if scores[best] > 0 else "widget"
252
+
253
+
254
+ # ============ MAIN SEARCH FUNCTIONS ============
255
+ def search(query, domain=None, max_results=MAX_RESULTS):
256
+ """
257
+ Main search function with auto-domain detection
258
+
259
+ Args:
260
+ query: Search query string
261
+ domain: Optional domain (widget, package, pattern, etc.)
262
+ max_results: Number of results to return
263
+
264
+ Returns:
265
+ Dict with domain, query, file, count, and results
266
+ """
267
+ if domain is None:
268
+ domain = detect_domain(query)
269
+
270
+ if domain not in CSV_CONFIG:
271
+ return {"error": f"Unknown domain: {domain}. Available: {', '.join(AVAILABLE_DOMAINS)}"}
272
+
273
+ config = CSV_CONFIG[domain]
274
+ filepath = DATA_DIR / config["file"]
275
+
276
+ if not filepath.exists():
277
+ return {"error": f"File not found: {filepath}", "domain": domain}
278
+
279
+ # Apply widget boosting for widget domain
280
+ boost_col = "Widget Name" if domain == "widget" else None
281
+ boost_query = query if domain == "widget" else None
282
+
283
+ results = _search_csv(
284
+ filepath,
285
+ config["search_cols"],
286
+ config["output_cols"],
287
+ query,
288
+ max_results,
289
+ boost_col=boost_col,
290
+ boost_query=boost_query
291
+ )
292
+
293
+ return {
294
+ "domain": domain,
295
+ "query": query,
296
+ "file": config["file"],
297
+ "count": len(results),
298
+ "results": results
299
+ }
300
+
301
+
302
+ def search_with_stack(query, stack, domain=None, max_results=MAX_RESULTS):
303
+ """
304
+ Search with stack-specific filtering (excludes conflicting packages)
305
+
306
+ Args:
307
+ query: Search query string
308
+ stack: Stack preference (riverpod, bloc, provider)
309
+ domain: Optional domain filter
310
+ max_results: Number of results to return
311
+
312
+ Returns:
313
+ Dict with filtered results
314
+ """
315
+ if stack not in STACK_EXCLUSIONS:
316
+ return {"error": f"Unknown stack: {stack}. Available: {', '.join(AVAILABLE_STACKS)}"}
317
+
318
+ result = search(query, domain, max_results * 2) # Get more to filter
319
+
320
+ if "error" in result:
321
+ return result
322
+
323
+ # Filter out conflicting packages
324
+ excluded = STACK_EXCLUSIONS[stack]
325
+ filtered_results = []
326
+
327
+ for item in result["results"]:
328
+ # Check package name field
329
+ pkg_name = item.get("pkg_name", "").lower()
330
+ if pkg_name not in excluded:
331
+ filtered_results.append(item)
332
+
333
+ if len(filtered_results) >= max_results:
334
+ break
335
+
336
+ result["results"] = filtered_results
337
+ result["count"] = len(filtered_results)
338
+ result["stack"] = stack
339
+ result["excluded"] = excluded
340
+
341
+ return result