mobile-best-practices 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/README.md +64 -0
  2. package/assets/data/anti-patterns.csv +114 -0
  3. package/assets/data/architectures.csv +50 -0
  4. package/assets/data/code-snippets.csv +80 -0
  5. package/assets/data/gradle-deps.csv +79 -0
  6. package/assets/data/libraries.csv +102 -0
  7. package/assets/data/performance.csv +229 -0
  8. package/assets/data/platforms/android.csv +247 -0
  9. package/assets/data/platforms/flutter.csv +55 -0
  10. package/assets/data/platforms/ios.csv +61 -0
  11. package/assets/data/platforms/react-native.csv +56 -0
  12. package/assets/data/project-templates.csv +19 -0
  13. package/assets/data/reasoning-rules.csv +57 -0
  14. package/assets/data/security.csv +438 -0
  15. package/assets/data/testing.csv +74 -0
  16. package/assets/data/ui-patterns.csv +92 -0
  17. package/assets/references/CHECKLIST.md +49 -0
  18. package/assets/references/CODE-RULES.md +123 -0
  19. package/assets/scripts/__pycache__/core.cpython-314.pyc +0 -0
  20. package/assets/scripts/core.py +432 -0
  21. package/assets/scripts/search.py +104 -0
  22. package/assets/skills/all.md +245 -0
  23. package/assets/skills/android.md +168 -0
  24. package/assets/skills/flutter.md +153 -0
  25. package/assets/skills/ios.md +149 -0
  26. package/assets/skills/react-native.md +154 -0
  27. package/assets/templates/base/quick-reference.md +41 -0
  28. package/assets/templates/base/skill-content.md +60 -0
  29. package/assets/templates/platforms/agent.json +11 -0
  30. package/assets/templates/platforms/antigravity.json +13 -0
  31. package/assets/templates/platforms/claude.json +27 -0
  32. package/assets/templates/platforms/codebuddy.json +11 -0
  33. package/assets/templates/platforms/codex.json +11 -0
  34. package/assets/templates/platforms/continue.json +11 -0
  35. package/assets/templates/platforms/copilot.json +11 -0
  36. package/assets/templates/platforms/cursor.json +11 -0
  37. package/assets/templates/platforms/gemini.json +11 -0
  38. package/assets/templates/platforms/kiro.json +11 -0
  39. package/assets/templates/platforms/opencode.json +11 -0
  40. package/assets/templates/platforms/qoder.json +11 -0
  41. package/assets/templates/platforms/roocode.json +11 -0
  42. package/assets/templates/platforms/trae.json +11 -0
  43. package/assets/templates/platforms/windsurf.json +11 -0
  44. package/dist/commands/init.d.ts +6 -0
  45. package/dist/commands/init.d.ts.map +1 -0
  46. package/dist/commands/init.js +94 -0
  47. package/dist/commands/init.js.map +1 -0
  48. package/dist/commands/update.d.ts +2 -0
  49. package/dist/commands/update.d.ts.map +1 -0
  50. package/dist/commands/update.js +28 -0
  51. package/dist/commands/update.js.map +1 -0
  52. package/dist/commands/versions.d.ts +2 -0
  53. package/dist/commands/versions.d.ts.map +1 -0
  54. package/dist/commands/versions.js +30 -0
  55. package/dist/commands/versions.js.map +1 -0
  56. package/dist/index.d.ts +3 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +27 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/types/index.d.ts +23 -0
  61. package/dist/types/index.d.ts.map +1 -0
  62. package/dist/types/index.js +24 -0
  63. package/dist/types/index.js.map +1 -0
  64. package/dist/utils/index.d.ts +9 -0
  65. package/dist/utils/index.d.ts.map +1 -0
  66. package/dist/utils/index.js +103 -0
  67. package/dist/utils/index.js.map +1 -0
  68. package/package.json +57 -0
@@ -0,0 +1,432 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Mobile Best Practices Core - BM25 search engine for mobile development best practices
5
+ """
6
+
7
+ import csv
8
+ import json
9
+ import re
10
+ from pathlib import Path
11
+ from math import log
12
+ from collections import defaultdict
13
+ from datetime import datetime
14
+
15
+ # ============ CONFIGURATION ============
16
+ DATA_DIR = Path(__file__).parent.parent / "data"
17
+ MAX_RESULTS = 3
18
+
19
+ CSV_CONFIG = {
20
+ "architecture": {
21
+ "file": "architectures.csv",
22
+ "search_cols": ["Name", "Platform", "Keywords", "Best For", "Tech Stack"],
23
+ "output_cols": ["Name", "Platform", "Complexity", "Team Size", "Keywords", "Best For", "Tech Stack", "Layers", "Structure", "Anti Patterns", "Notes"]
24
+ },
25
+ "ui": {
26
+ "file": "ui-patterns.csv",
27
+ "search_cols": ["Pattern Name", "Platform", "Category", "Keywords", "Use Case"],
28
+ "output_cols": ["Pattern Name", "Platform", "Category", "Keywords", "Use Case", "Components", "Implementation Notes", "Accessibility"]
29
+ },
30
+ "template": {
31
+ "file": "project-templates.csv",
32
+ "search_cols": ["Template Name", "Platform", "Architecture", "Tech Stack", "Features Included"],
33
+ "output_cols": ["Template Name", "Platform", "Architecture", "Tech Stack", "Modules", "Folder Structure", "Features Included", "Key Dependencies"]
34
+ },
35
+ "antipattern": {
36
+ "file": "anti-patterns.csv",
37
+ "search_cols": ["Name", "Platform", "Category", "Keywords", "Description"],
38
+ "output_cols": ["Name", "Platform", "Category", "Severity", "Description", "Bad Example", "Good Example", "Why Bad", "Fix"]
39
+ },
40
+ "reasoning": {
41
+ "file": "reasoning-rules.csv",
42
+ "search_cols": ["Product Type", "Platform", "Keywords", "Key Features"],
43
+ "output_cols": ["Product Type", "Platform", "Recommended Arch", "Recommended UI", "Color Mood", "Key Features", "Anti Patterns", "Key Dependencies", "Notes"]
44
+ },
45
+ "library": {
46
+ "file": "libraries.csv",
47
+ "search_cols": ["Name", "Platform", "Category", "Keywords", "Description"],
48
+ "output_cols": ["Name", "Platform", "Category", "Keywords", "Description", "Gradle/Pod/Pub", "Alternative", "Stars", "Notes"]
49
+ },
50
+ "performance": {
51
+ "file": "performance.csv",
52
+ "search_cols": ["Category", "Issue", "Platform", "Keywords", "Description"],
53
+ "output_cols": ["Category", "Issue", "Platform", "Severity", "Description", "Do", "Dont", "Code Good", "Code Bad", "Metric"]
54
+ },
55
+ "testing": {
56
+ "file": "testing.csv",
57
+ "search_cols": ["Category", "Pattern", "Platform", "Keywords", "Description"],
58
+ "output_cols": ["Category", "Pattern", "Platform", "Description", "Framework", "Code Example", "Anti Pattern", "Notes"]
59
+ },
60
+ "security": {
61
+ "file": "security.csv",
62
+ "search_cols": ["Category", "Threat", "Platform", "Keywords", "Description"],
63
+ "output_cols": ["Category", "Threat", "Platform", "Severity", "Description", "Mitigation", "Code Good", "Code Bad", "OWASP Ref"]
64
+ },
65
+ "snippet": {
66
+ "file": "code-snippets.csv",
67
+ "search_cols": ["Name", "Category", "Keywords", "Description"],
68
+ "output_cols": ["ID", "Name", "Platform", "Category", "Description", "Code", "Imports", "Notes"]
69
+ },
70
+ "gradle": {
71
+ "file": "gradle-deps.csv",
72
+ "search_cols": ["Name", "Category", "Keywords"],
73
+ "output_cols": ["Name", "Category", "Version Catalog Key", "Implementation", "KSP/KAPT", "Version", "Notes"]
74
+ }
75
+ }
76
+
77
+ PLATFORM_CONFIG = {
78
+ "android": {"file": "platforms/android.csv"},
79
+ "ios": {"file": "platforms/ios.csv"},
80
+ "flutter": {"file": "platforms/flutter.csv"},
81
+ "react-native": {"file": "platforms/react-native.csv"}
82
+ }
83
+
84
+ _PLATFORM_COLS = {
85
+ "search_cols": ["Category", "Guideline", "Description", "Do", "Dont"],
86
+ "output_cols": ["Category", "Guideline", "Description", "Do", "Dont", "Code Good", "Code Bad", "Severity", "Docs URL"]
87
+ }
88
+
89
+ AVAILABLE_PLATFORMS = list(PLATFORM_CONFIG.keys())
90
+
91
+ # Stack-to-platform mapping for --stack search
92
+ STACK_MAP = {
93
+ "compose": "android",
94
+ "jetpack-compose": "android",
95
+ "material3": "android",
96
+ "hilt": "android",
97
+ "room": "android",
98
+ "kotlin": "android",
99
+ "swiftui": "ios",
100
+ "combine": "ios",
101
+ "uikit": "ios",
102
+ "swift": "ios",
103
+ "flutter": "flutter",
104
+ "dart": "flutter",
105
+ "bloc": "flutter",
106
+ "riverpod": "flutter",
107
+ "react-native": "react-native",
108
+ "rn": "react-native",
109
+ "hooks": "react-native",
110
+ "typescript": "react-native",
111
+ "redux": "react-native",
112
+ }
113
+
114
+ AVAILABLE_STACKS = list(STACK_MAP.keys())
115
+
116
+
117
+ # ============ BM25 IMPLEMENTATION ============
118
+ class BM25:
119
+ """BM25 ranking algorithm for text search"""
120
+
121
+ def __init__(self, k1=1.5, b=0.75):
122
+ self.k1 = k1
123
+ self.b = b
124
+ self.corpus = []
125
+ self.doc_lengths = []
126
+ self.avgdl = 0
127
+ self.idf = {}
128
+ self.doc_freqs = defaultdict(int)
129
+ self.N = 0
130
+
131
+ def tokenize(self, text):
132
+ """Lowercase, split, remove punctuation, filter short words"""
133
+ text = re.sub(r'[^\w\s]', ' ', str(text).lower())
134
+ return [w for w in text.split() if len(w) > 2]
135
+
136
+ def fit(self, documents):
137
+ """Build BM25 index from documents"""
138
+ self.corpus = [self.tokenize(doc) for doc in documents]
139
+ self.N = len(self.corpus)
140
+ if self.N == 0:
141
+ return
142
+ self.doc_lengths = [len(doc) for doc in self.corpus]
143
+ self.avgdl = sum(self.doc_lengths) / self.N
144
+
145
+ for doc in self.corpus:
146
+ seen = set()
147
+ for word in doc:
148
+ if word not in seen:
149
+ self.doc_freqs[word] += 1
150
+ seen.add(word)
151
+
152
+ for word, freq in self.doc_freqs.items():
153
+ self.idf[word] = log((self.N - freq + 0.5) / (freq + 0.5) + 1)
154
+
155
+ def score(self, query):
156
+ """Score all documents against query"""
157
+ query_tokens = self.tokenize(query)
158
+ scores = []
159
+
160
+ for idx, doc in enumerate(self.corpus):
161
+ score = 0
162
+ doc_len = self.doc_lengths[idx]
163
+ term_freqs = defaultdict(int)
164
+ for word in doc:
165
+ term_freqs[word] += 1
166
+
167
+ for token in query_tokens:
168
+ if token in self.idf:
169
+ tf = term_freqs[token]
170
+ idf = self.idf[token]
171
+ numerator = tf * (self.k1 + 1)
172
+ denominator = tf + self.k1 * (1 - self.b + self.b * doc_len / self.avgdl)
173
+ score += idf * numerator / denominator
174
+
175
+ scores.append((idx, score))
176
+
177
+ return sorted(scores, key=lambda x: x[1], reverse=True)
178
+
179
+
180
+ # ============ SEARCH FUNCTIONS ============
181
+ def _load_csv(filepath):
182
+ """Load CSV and return list of dicts"""
183
+ with open(filepath, 'r', encoding='utf-8') as f:
184
+ return list(csv.DictReader(f))
185
+
186
+
187
+ def _search_csv(filepath, search_cols, output_cols, query, max_results):
188
+ """Core search function using BM25"""
189
+ if not filepath.exists():
190
+ return []
191
+
192
+ data = _load_csv(filepath)
193
+
194
+ # Build documents from search columns
195
+ documents = [" ".join(str(row.get(col, "")) for col in search_cols) for row in data]
196
+
197
+ # BM25 search
198
+ bm25 = BM25()
199
+ bm25.fit(documents)
200
+ ranked = bm25.score(query)
201
+
202
+ # Get top results with score > 0
203
+ results = []
204
+ for idx, score in ranked[:max_results]:
205
+ if score > 0:
206
+ row = data[idx]
207
+ results.append({col: row.get(col, "") for col in output_cols if col in row})
208
+
209
+ return results
210
+
211
+
212
+ def detect_domain(query):
213
+ """Auto-detect the most relevant domain from query"""
214
+ query_lower = query.lower()
215
+
216
+ domain_keywords = {
217
+ "architecture": ["mvvm", "mvi", "viper", "bloc", "clean", "architecture", "repository", "coordinator", "redux", "tca", "layer", "module"],
218
+ "ui": ["button", "navigation", "bottom sheet", "tab", "list", "card", "dialog", "modal", "drawer", "scaffold", "appbar", "toolbar"],
219
+ "template": ["project", "template", "setup", "scaffold", "starter", "boilerplate", "create", "new app", "init"],
220
+ "antipattern": ["anti-pattern", "antipattern", "mistake", "bad practice", "wrong", "avoid", "smell", "god class", "leak"],
221
+ "reasoning": ["ecommerce", "e-commerce", "banking", "fintech", "social", "healthcare", "delivery", "fitness", "education", "food", "chat", "streaming"],
222
+ "library": ["library", "dependency", "package", "retrofit", "hilt", "room", "coil", "ktor", "alamofire", "dio", "riverpod", "redux"],
223
+ "performance": ["performance", "memory", "battery", "startup", "render", "fps", "lag", "slow", "optimize", "profil", "baseline"],
224
+ "testing": ["test", "unit test", "ui test", "espresso", "xctest", "mockito", "junit", "widget test", "integration"],
225
+ "security": ["security", "encrypt", "keychain", "keystore", "proguard", "obfuscate", "ssl", "pin", "biometric", "auth token"],
226
+ "snippet": ["snippet", "code", "example", "template code", "viewmodel code", "compose screen", "room setup", "hilt module", "bottom nav", "paging", "datastore", "theme code"],
227
+ "gradle": ["gradle", "dependency", "implementation", "ksp", "kapt", "version catalog", "libs.", "bom", "plugin", "classpath"]
228
+ }
229
+
230
+ scores = {domain: sum(1 for kw in keywords if kw in query_lower) for domain, keywords in domain_keywords.items()}
231
+ best = max(scores, key=scores.get)
232
+ return best if scores[best] > 0 else "architecture"
233
+
234
+
235
+ def search(query, domain=None, max_results=MAX_RESULTS, filter_platform=None):
236
+ """Main search function with auto-domain detection and optional platform filter"""
237
+ if domain is None:
238
+ domain = detect_domain(query)
239
+
240
+ config = CSV_CONFIG.get(domain, CSV_CONFIG["architecture"])
241
+ filepath = DATA_DIR / config["file"]
242
+
243
+ if not filepath.exists():
244
+ return {"error": f"File not found: {filepath}", "domain": domain}
245
+
246
+ results = _search_csv(filepath, config["search_cols"], config["output_cols"], query, max_results * 3 if filter_platform else max_results)
247
+
248
+ # Filter by platform if specified
249
+ if filter_platform and results:
250
+ filter_lower = filter_platform.lower()
251
+ results = [r for r in results if any(
252
+ filter_lower in str(v).lower()
253
+ for k, v in r.items()
254
+ if k.lower() == "platform"
255
+ )][:max_results]
256
+
257
+ return {
258
+ "domain": domain,
259
+ "query": query,
260
+ "file": config["file"],
261
+ "count": len(results),
262
+ "results": results
263
+ }
264
+
265
+
266
+ def search_platform(query, platform, max_results=MAX_RESULTS):
267
+ """Search platform-specific guidelines"""
268
+ if platform not in PLATFORM_CONFIG:
269
+ return {"error": f"Unknown platform: {platform}. Available: {', '.join(AVAILABLE_PLATFORMS)}"}
270
+
271
+ filepath = DATA_DIR / PLATFORM_CONFIG[platform]["file"]
272
+
273
+ if not filepath.exists():
274
+ return {"error": f"Platform file not found: {filepath}", "platform": platform}
275
+
276
+ results = _search_csv(filepath, _PLATFORM_COLS["search_cols"], _PLATFORM_COLS["output_cols"], query, max_results)
277
+
278
+ return {
279
+ "domain": "platform",
280
+ "platform": platform,
281
+ "query": query,
282
+ "file": PLATFORM_CONFIG[platform]["file"],
283
+ "count": len(results),
284
+ "results": results
285
+ }
286
+
287
+
288
+ def search_stack(query, stack, max_results=MAX_RESULTS):
289
+ """Search filtered by tech stack (maps stack to platform + adds stack keywords)"""
290
+ stack_lower = stack.lower()
291
+
292
+ if stack_lower not in STACK_MAP:
293
+ return {"error": f"Unknown stack: {stack}. Available: {', '.join(AVAILABLE_STACKS)}"}
294
+
295
+ platform = STACK_MAP[stack_lower]
296
+
297
+ # Search platform guidelines first
298
+ platform_results = search_platform(f"{query} {stack}", platform, max_results)
299
+
300
+ # Also search across domains filtered by platform
301
+ domain_results = search(f"{query} {stack}", filter_platform=platform, max_results=max_results)
302
+
303
+ # Merge results
304
+ all_results = []
305
+ if platform_results.get("results"):
306
+ all_results.extend(platform_results["results"])
307
+ if domain_results.get("results"):
308
+ all_results.extend(domain_results["results"])
309
+
310
+ return {
311
+ "domain": "stack",
312
+ "stack": stack,
313
+ "platform": platform,
314
+ "query": query,
315
+ "count": len(all_results[:max_results]),
316
+ "results": all_results[:max_results]
317
+ }
318
+
319
+
320
+ def persist_blueprint(query, output_dir=None, project_name=None, page=None):
321
+ """Generate and persist architecture blueprint from search results"""
322
+ if output_dir is None:
323
+ output_dir = Path.cwd() / "architecture-blueprint"
324
+
325
+ output_dir = Path(output_dir)
326
+
327
+ # Run multi-domain search
328
+ domains_to_search = ["reasoning", "architecture", "snippet", "gradle", "performance", "security", "antipattern"]
329
+ all_results = {}
330
+
331
+ for domain in domains_to_search:
332
+ result = search(query, domain=domain, max_results=5)
333
+ if result.get("results"):
334
+ all_results[domain] = result["results"]
335
+
336
+ # Detect platform
337
+ platform = None
338
+ for kw in ["android", "ios", "flutter", "react-native", "react native"]:
339
+ if kw in query.lower():
340
+ platform = kw.replace(" ", "-")
341
+ break
342
+ if not platform:
343
+ platform = "android"
344
+
345
+ # Get platform guidelines
346
+ platform_result = search_platform(query, platform, max_results=5)
347
+ if platform_result.get("results"):
348
+ all_results["platform"] = platform_result["results"]
349
+
350
+ # Generate blueprint markdown
351
+ pname = project_name or "MyApp"
352
+ lines = [
353
+ f"# Architecture Blueprint - {pname}",
354
+ f"",
355
+ f"**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M')}",
356
+ f"**Query:** {query}",
357
+ f"**Platform:** {platform}",
358
+ f"",
359
+ ]
360
+
361
+ if "reasoning" in all_results:
362
+ lines.append("## Product Recommendation")
363
+ for r in all_results["reasoning"][:1]:
364
+ for k, v in r.items():
365
+ lines.append(f"- **{k}:** {v}")
366
+ lines.append("")
367
+
368
+ if "architecture" in all_results:
369
+ lines.append("## Architecture")
370
+ for r in all_results["architecture"][:2]:
371
+ for k, v in r.items():
372
+ lines.append(f"- **{k}:** {v}")
373
+ lines.append("")
374
+
375
+ if "gradle" in all_results:
376
+ lines.append("## Dependencies")
377
+ for r in all_results["gradle"]:
378
+ name = r.get("Name", "")
379
+ impl = r.get("Implementation", "")
380
+ lines.append(f"- **{name}:** `{impl}`")
381
+ lines.append("")
382
+
383
+ if "performance" in all_results:
384
+ lines.append("## Performance Rules")
385
+ for r in all_results["performance"][:3]:
386
+ issue = r.get("Issue", "")
387
+ do = r.get("Do", "")
388
+ lines.append(f"- **{issue}:** {do}")
389
+ lines.append("")
390
+
391
+ if "security" in all_results:
392
+ lines.append("## Security Checklist")
393
+ for r in all_results["security"][:3]:
394
+ threat = r.get("Threat", "")
395
+ mitigation = r.get("Mitigation", "")
396
+ lines.append(f"- **{threat}:** {mitigation}")
397
+ lines.append("")
398
+
399
+ if "antipattern" in all_results:
400
+ lines.append("## Anti-Patterns to Avoid")
401
+ for r in all_results["antipattern"][:3]:
402
+ name = r.get("Name", "")
403
+ fix = r.get("Fix", "")
404
+ lines.append(f"- **{name}:** {fix}")
405
+ lines.append("")
406
+
407
+ if "platform" in all_results:
408
+ lines.append(f"## {platform.title()} Best Practices")
409
+ for r in all_results["platform"][:5]:
410
+ guideline = r.get("Guideline", "")
411
+ desc = r.get("Do", "")
412
+ lines.append(f"- **{guideline}:** {desc}")
413
+ lines.append("")
414
+
415
+ content = "\n".join(lines)
416
+
417
+ # Write files
418
+ if page:
419
+ pages_dir = output_dir / "pages"
420
+ pages_dir.mkdir(parents=True, exist_ok=True)
421
+ filepath = pages_dir / f"{page}.md"
422
+ else:
423
+ output_dir.mkdir(parents=True, exist_ok=True)
424
+ filepath = output_dir / "MASTER.md"
425
+
426
+ filepath.write_text(content, encoding="utf-8")
427
+
428
+ return {
429
+ "file": str(filepath),
430
+ "sections": list(all_results.keys()),
431
+ "total_entries": sum(len(v) for v in all_results.values()),
432
+ }
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Mobile Best Practices Search - BM25 search engine for mobile development
5
+ Usage: python search.py "<query>" [--domain <domain>] [--platform <platform>] [--stack <stack>] [--persist] [--max-results 3]
6
+
7
+ Domains: architecture, ui, template, antipattern, reasoning, library, performance, testing, security, snippet, gradle
8
+ Platforms: android, ios, flutter, react-native
9
+ Stacks: compose, jetpack-compose, material3, hilt, room, kotlin, swiftui, combine, uikit, swift, flutter, dart, bloc, riverpod, react-native, rn, hooks, typescript, redux
10
+ """
11
+
12
+ import argparse
13
+ from core import (
14
+ CSV_CONFIG, AVAILABLE_PLATFORMS, AVAILABLE_STACKS, MAX_RESULTS,
15
+ search, search_platform, search_stack, persist_blueprint
16
+ )
17
+
18
+
19
+ def format_output(result):
20
+ """Format results for Claude consumption (token-optimized)"""
21
+ if "error" in result:
22
+ return f"Error: {result['error']}"
23
+
24
+ output = []
25
+ if result.get("domain") == "platform":
26
+ output.append(f"## Mobile Best Practices - Platform Guidelines")
27
+ output.append(f"**Platform:** {result['platform']} | **Query:** {result['query']}")
28
+ elif result.get("domain") == "stack":
29
+ output.append(f"## Mobile Best Practices - Stack Search")
30
+ output.append(f"**Stack:** {result['stack']} ({result.get('platform', '')}) | **Query:** {result['query']}")
31
+ else:
32
+ output.append(f"## Mobile Best Practices - Search Results")
33
+ output.append(f"**Domain:** {result['domain']} | **Query:** {result['query']}")
34
+
35
+ file_info = result.get('file', '')
36
+ if file_info:
37
+ output.append(f"**Source:** {file_info} | **Found:** {result['count']} results\n")
38
+ else:
39
+ output.append(f"**Found:** {result['count']} results\n")
40
+
41
+ for i, row in enumerate(result['results'], 1):
42
+ output.append(f"### Result {i}")
43
+ for key, value in row.items():
44
+ value_str = str(value)
45
+ if len(value_str) > 300:
46
+ value_str = value_str[:300] + "..."
47
+ output.append(f"- **{key}:** {value_str}")
48
+ output.append("")
49
+
50
+ return "\n".join(output)
51
+
52
+
53
+ if __name__ == "__main__":
54
+ parser = argparse.ArgumentParser(description="Mobile Best Practices Search")
55
+ parser.add_argument("query", help="Search query")
56
+ parser.add_argument("--domain", "-d", choices=list(CSV_CONFIG.keys()), help="Search domain")
57
+ parser.add_argument("--platform", "-p", choices=AVAILABLE_PLATFORMS, help="Platform-specific search (android, ios, flutter, react-native)")
58
+ parser.add_argument("--stack", "-s", choices=AVAILABLE_STACKS, help="Stack-specific search (compose, swiftui, flutter, react-native, etc.)")
59
+ parser.add_argument("--max-results", "-n", type=int, default=MAX_RESULTS, help="Max results (default: 3)")
60
+ parser.add_argument("--filter-platform", "-fp", choices=AVAILABLE_PLATFORMS, help="Filter any domain results by platform")
61
+ parser.add_argument("--json", action="store_true", help="Output as JSON")
62
+ parser.add_argument("--persist", action="store_true", help="Save results to architecture blueprint file")
63
+ parser.add_argument("--project-name", "-pn", help="Project name for blueprint (default: MyApp)")
64
+ parser.add_argument("--page", help="Generate page-specific blueprint override")
65
+
66
+ args = parser.parse_args()
67
+
68
+ # Persist mode
69
+ if args.persist:
70
+ result = persist_blueprint(
71
+ args.query,
72
+ project_name=args.project_name,
73
+ page=args.page
74
+ )
75
+ if args.json:
76
+ import json
77
+ print(json.dumps(result, indent=2, ensure_ascii=False))
78
+ else:
79
+ print(f"Blueprint saved to: {result['file']}")
80
+ print(f"Sections: {', '.join(result['sections'])}")
81
+ print(f"Total entries: {result['total_entries']}")
82
+ # Stack search
83
+ elif args.stack:
84
+ result = search_stack(args.query, args.stack, args.max_results)
85
+ if args.json:
86
+ import json
87
+ print(json.dumps(result, indent=2, ensure_ascii=False))
88
+ else:
89
+ print(format_output(result))
90
+ # Platform search takes priority
91
+ elif args.platform:
92
+ result = search_platform(args.query, args.platform, args.max_results)
93
+ if args.json:
94
+ import json
95
+ print(json.dumps(result, indent=2, ensure_ascii=False))
96
+ else:
97
+ print(format_output(result))
98
+ else:
99
+ result = search(args.query, args.domain, args.max_results, filter_platform=args.filter_platform)
100
+ if args.json:
101
+ import json
102
+ print(json.dumps(result, indent=2, ensure_ascii=False))
103
+ else:
104
+ print(format_output(result))