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.
- package/README.md +64 -0
- package/assets/data/anti-patterns.csv +114 -0
- package/assets/data/architectures.csv +50 -0
- package/assets/data/code-snippets.csv +80 -0
- package/assets/data/gradle-deps.csv +79 -0
- package/assets/data/libraries.csv +102 -0
- package/assets/data/performance.csv +229 -0
- package/assets/data/platforms/android.csv +247 -0
- package/assets/data/platforms/flutter.csv +55 -0
- package/assets/data/platforms/ios.csv +61 -0
- package/assets/data/platforms/react-native.csv +56 -0
- package/assets/data/project-templates.csv +19 -0
- package/assets/data/reasoning-rules.csv +57 -0
- package/assets/data/security.csv +438 -0
- package/assets/data/testing.csv +74 -0
- package/assets/data/ui-patterns.csv +92 -0
- package/assets/references/CHECKLIST.md +49 -0
- package/assets/references/CODE-RULES.md +123 -0
- package/assets/scripts/__pycache__/core.cpython-314.pyc +0 -0
- package/assets/scripts/core.py +432 -0
- package/assets/scripts/search.py +104 -0
- package/assets/skills/all.md +245 -0
- package/assets/skills/android.md +168 -0
- package/assets/skills/flutter.md +153 -0
- package/assets/skills/ios.md +149 -0
- package/assets/skills/react-native.md +154 -0
- package/assets/templates/base/quick-reference.md +41 -0
- package/assets/templates/base/skill-content.md +60 -0
- package/assets/templates/platforms/agent.json +11 -0
- package/assets/templates/platforms/antigravity.json +13 -0
- package/assets/templates/platforms/claude.json +27 -0
- package/assets/templates/platforms/codebuddy.json +11 -0
- package/assets/templates/platforms/codex.json +11 -0
- package/assets/templates/platforms/continue.json +11 -0
- package/assets/templates/platforms/copilot.json +11 -0
- package/assets/templates/platforms/cursor.json +11 -0
- package/assets/templates/platforms/gemini.json +11 -0
- package/assets/templates/platforms/kiro.json +11 -0
- package/assets/templates/platforms/opencode.json +11 -0
- package/assets/templates/platforms/qoder.json +11 -0
- package/assets/templates/platforms/roocode.json +11 -0
- package/assets/templates/platforms/trae.json +11 -0
- package/assets/templates/platforms/windsurf.json +11 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +94 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +28 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/versions.d.ts +2 -0
- package/dist/commands/versions.d.ts.map +1 -0
- package/dist/commands/versions.js +30 -0
- package/dist/commands/versions.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +23 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +24 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +103 -0
- package/dist/utils/index.js.map +1 -0
- 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))
|