heraspec 0.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.
- package/LICENSE +22 -0
- package/README.md +57 -0
- package/bin/heraspec.js +3803 -0
- package/bin/heraspec.js.map +7 -0
- package/dist/core/templates/skills/CHANGELOG.md +117 -0
- package/dist/core/templates/skills/README-template.md +58 -0
- package/dist/core/templates/skills/README.md +36 -0
- package/dist/core/templates/skills/content-optimization-skill.md +104 -0
- package/dist/core/templates/skills/data/charts.csv +26 -0
- package/dist/core/templates/skills/data/colors.csv +97 -0
- package/dist/core/templates/skills/data/landing.csv +31 -0
- package/dist/core/templates/skills/data/pages-proposed.csv +22 -0
- package/dist/core/templates/skills/data/pages.csv +10 -0
- package/dist/core/templates/skills/data/products.csv +97 -0
- package/dist/core/templates/skills/data/prompts.csv +24 -0
- package/dist/core/templates/skills/data/stacks/flutter.csv +53 -0
- package/dist/core/templates/skills/data/stacks/html-tailwind.csv +56 -0
- package/dist/core/templates/skills/data/stacks/nextjs.csv +53 -0
- package/dist/core/templates/skills/data/stacks/react-native.csv +52 -0
- package/dist/core/templates/skills/data/stacks/react.csv +54 -0
- package/dist/core/templates/skills/data/stacks/svelte.csv +54 -0
- package/dist/core/templates/skills/data/stacks/swiftui.csv +51 -0
- package/dist/core/templates/skills/data/stacks/vue.csv +50 -0
- package/dist/core/templates/skills/data/styles.csv +59 -0
- package/dist/core/templates/skills/data/typography.csv +58 -0
- package/dist/core/templates/skills/data/ux-guidelines.csv +100 -0
- package/dist/core/templates/skills/documents-skill.md +114 -0
- package/dist/core/templates/skills/e2e-test-skill.md +119 -0
- package/dist/core/templates/skills/integration-test-skill.md +118 -0
- package/dist/core/templates/skills/module-codebase-skill.md +110 -0
- package/dist/core/templates/skills/scripts/CODE_EXPLANATION.md +394 -0
- package/dist/core/templates/skills/scripts/SEARCH_ALGORITHMS_COMPARISON.md +421 -0
- package/dist/core/templates/skills/scripts/SEARCH_MODES_GUIDE.md +238 -0
- package/dist/core/templates/skills/scripts/core.py +385 -0
- package/dist/core/templates/skills/scripts/search.py +73 -0
- package/dist/core/templates/skills/suggestion-skill.md +118 -0
- package/dist/core/templates/skills/templates/accessibility-checklist.md +40 -0
- package/dist/core/templates/skills/templates/example-prompt-full-theme.md +333 -0
- package/dist/core/templates/skills/templates/page-types-guide.md +338 -0
- package/dist/core/templates/skills/templates/pages-proposed-summary.md +273 -0
- package/dist/core/templates/skills/templates/pre-delivery-checklist.md +42 -0
- package/dist/core/templates/skills/templates/prompt-template-full-theme.md +313 -0
- package/dist/core/templates/skills/templates/responsive-design.md +40 -0
- package/dist/core/templates/skills/ui-ux-skill.md +584 -0
- package/dist/core/templates/skills/unit-test-skill.md +111 -0
- package/dist/index.js +1736 -0
- package/package.json +71 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
UI/UX Builder Core - BM25 search engine for UI/UX style guides
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import csv
|
|
8
|
+
import re
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from math import log
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
|
|
13
|
+
# Optional dependencies for vector search
|
|
14
|
+
try:
|
|
15
|
+
from sentence_transformers import SentenceTransformer
|
|
16
|
+
import numpy as np
|
|
17
|
+
from sklearn.metrics.pairwise import cosine_similarity
|
|
18
|
+
VECTOR_AVAILABLE = True
|
|
19
|
+
except ImportError:
|
|
20
|
+
VECTOR_AVAILABLE = False
|
|
21
|
+
|
|
22
|
+
# ============ CONFIGURATION ============
|
|
23
|
+
DATA_DIR = Path(__file__).parent.parent / "data"
|
|
24
|
+
MAX_RESULTS = 3
|
|
25
|
+
|
|
26
|
+
CSV_CONFIG = {
|
|
27
|
+
"style": {
|
|
28
|
+
"file": "styles.csv",
|
|
29
|
+
"search_cols": ["Style Category", "Keywords", "Best For", "Type"],
|
|
30
|
+
"output_cols": ["Style Category", "Type", "Keywords", "Primary Colors", "Effects & Animation", "Best For", "Performance", "Accessibility", "Framework Compatibility", "Complexity"]
|
|
31
|
+
},
|
|
32
|
+
"prompt": {
|
|
33
|
+
"file": "prompts.csv",
|
|
34
|
+
"search_cols": ["Style Category", "AI Prompt Keywords (Copy-Paste Ready)", "CSS/Technical Keywords"],
|
|
35
|
+
"output_cols": ["Style Category", "AI Prompt Keywords (Copy-Paste Ready)", "CSS/Technical Keywords", "Implementation Checklist"]
|
|
36
|
+
},
|
|
37
|
+
"color": {
|
|
38
|
+
"file": "colors.csv",
|
|
39
|
+
"search_cols": ["Product Type", "Keywords", "Notes"],
|
|
40
|
+
"output_cols": ["Product Type", "Keywords", "Primary (Hex)", "Secondary (Hex)", "CTA (Hex)", "Background (Hex)", "Text (Hex)", "Border (Hex)", "Notes"]
|
|
41
|
+
},
|
|
42
|
+
"chart": {
|
|
43
|
+
"file": "charts.csv",
|
|
44
|
+
"search_cols": ["Data Type", "Keywords", "Best Chart Type", "Accessibility Notes"],
|
|
45
|
+
"output_cols": ["Data Type", "Keywords", "Best Chart Type", "Secondary Options", "Color Guidance", "Accessibility Notes", "Library Recommendation", "Interactive Level"]
|
|
46
|
+
},
|
|
47
|
+
"landing": {
|
|
48
|
+
"file": "landing.csv",
|
|
49
|
+
"search_cols": ["Pattern Name", "Keywords", "Conversion Optimization", "Section Order"],
|
|
50
|
+
"output_cols": ["Pattern Name", "Keywords", "Section Order", "Primary CTA Placement", "Color Strategy", "Conversion Optimization"]
|
|
51
|
+
},
|
|
52
|
+
"product": {
|
|
53
|
+
"file": "products.csv",
|
|
54
|
+
"search_cols": ["Product Type", "Keywords", "Primary Style Recommendation", "Key Considerations"],
|
|
55
|
+
"output_cols": ["Product Type", "Keywords", "Primary Style Recommendation", "Secondary Styles", "Landing Page Pattern", "Dashboard Style (if applicable)", "Color Palette Focus"]
|
|
56
|
+
},
|
|
57
|
+
"ux": {
|
|
58
|
+
"file": "ux-guidelines.csv",
|
|
59
|
+
"search_cols": ["Category", "Issue", "Description", "Platform"],
|
|
60
|
+
"output_cols": ["Category", "Issue", "Platform", "Description", "Do", "Don't", "Code Example Good", "Code Example Bad", "Severity"]
|
|
61
|
+
},
|
|
62
|
+
"typography": {
|
|
63
|
+
"file": "typography.csv",
|
|
64
|
+
"search_cols": ["Font Pairing Name", "Category", "Mood/Style Keywords", "Best For", "Heading Font", "Body Font"],
|
|
65
|
+
"output_cols": ["Font Pairing Name", "Category", "Heading Font", "Body Font", "Mood/Style Keywords", "Best For", "Google Fonts URL", "CSS Import", "Tailwind Config", "Notes"]
|
|
66
|
+
},
|
|
67
|
+
"pages": {
|
|
68
|
+
"file": "pages.csv",
|
|
69
|
+
"search_cols": ["Page Type", "Keywords", "Section Order", "Key Components", "Layout Pattern", "Best For"],
|
|
70
|
+
"output_cols": ["Page Type", "Keywords", "Section Order", "Key Components", "Layout Pattern", "Color Strategy", "Recommended Effects", "Best For", "Considerations"]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
STACK_CONFIG = {
|
|
75
|
+
"html-tailwind": {"file": "stacks/html-tailwind.csv"},
|
|
76
|
+
"react": {"file": "stacks/react.csv"},
|
|
77
|
+
"nextjs": {"file": "stacks/nextjs.csv"},
|
|
78
|
+
"vue": {"file": "stacks/vue.csv"},
|
|
79
|
+
"svelte": {"file": "stacks/svelte.csv"},
|
|
80
|
+
"swiftui": {"file": "stacks/swiftui.csv"},
|
|
81
|
+
"react-native": {"file": "stacks/react-native.csv"},
|
|
82
|
+
"flutter": {"file": "stacks/flutter.csv"}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# Common columns for all stacks
|
|
86
|
+
_STACK_COLS = {
|
|
87
|
+
"search_cols": ["Category", "Guideline", "Description", "Do", "Don't"],
|
|
88
|
+
"output_cols": ["Category", "Guideline", "Description", "Do", "Don't", "Code Good", "Code Bad", "Severity", "Docs URL"]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
AVAILABLE_STACKS = list(STACK_CONFIG.keys())
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ============ BM25 IMPLEMENTATION ============
|
|
95
|
+
class BM25:
|
|
96
|
+
"""BM25 ranking algorithm for text search"""
|
|
97
|
+
|
|
98
|
+
def __init__(self, k1=1.5, b=0.75):
|
|
99
|
+
self.k1 = k1
|
|
100
|
+
self.b = b
|
|
101
|
+
self.corpus = []
|
|
102
|
+
self.doc_lengths = []
|
|
103
|
+
self.avgdl = 0
|
|
104
|
+
self.idf = {}
|
|
105
|
+
self.doc_freqs = defaultdict(int)
|
|
106
|
+
self.N = 0
|
|
107
|
+
|
|
108
|
+
def tokenize(self, text):
|
|
109
|
+
"""Lowercase, split, remove punctuation, filter short words"""
|
|
110
|
+
text = re.sub(r'[^\w\s]', ' ', str(text).lower())
|
|
111
|
+
return [w for w in text.split() if len(w) > 2]
|
|
112
|
+
|
|
113
|
+
def fit(self, documents):
|
|
114
|
+
"""Build BM25 index from documents"""
|
|
115
|
+
self.corpus = [self.tokenize(doc) for doc in documents]
|
|
116
|
+
self.N = len(self.corpus)
|
|
117
|
+
if self.N == 0:
|
|
118
|
+
return
|
|
119
|
+
self.doc_lengths = [len(doc) for doc in self.corpus]
|
|
120
|
+
self.avgdl = sum(self.doc_lengths) / self.N
|
|
121
|
+
|
|
122
|
+
for doc in self.corpus:
|
|
123
|
+
seen = set()
|
|
124
|
+
for word in doc:
|
|
125
|
+
if word not in seen:
|
|
126
|
+
self.doc_freqs[word] += 1
|
|
127
|
+
seen.add(word)
|
|
128
|
+
|
|
129
|
+
for word, freq in self.doc_freqs.items():
|
|
130
|
+
self.idf[word] = log((self.N - freq + 0.5) / (freq + 0.5) + 1)
|
|
131
|
+
|
|
132
|
+
def score(self, query):
|
|
133
|
+
"""Score all documents against query"""
|
|
134
|
+
query_tokens = self.tokenize(query)
|
|
135
|
+
scores = []
|
|
136
|
+
|
|
137
|
+
for idx, doc in enumerate(self.corpus):
|
|
138
|
+
score = 0
|
|
139
|
+
doc_len = self.doc_lengths[idx]
|
|
140
|
+
term_freqs = defaultdict(int)
|
|
141
|
+
for word in doc:
|
|
142
|
+
term_freqs[word] += 1
|
|
143
|
+
|
|
144
|
+
for token in query_tokens:
|
|
145
|
+
if token in self.idf:
|
|
146
|
+
tf = term_freqs[token]
|
|
147
|
+
idf = self.idf[token]
|
|
148
|
+
numerator = tf * (self.k1 + 1)
|
|
149
|
+
denominator = tf + self.k1 * (1 - self.b + self.b * doc_len / self.avgdl)
|
|
150
|
+
score += idf * numerator / denominator
|
|
151
|
+
|
|
152
|
+
scores.append((idx, score))
|
|
153
|
+
|
|
154
|
+
return sorted(scores, key=lambda x: x[1], reverse=True)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ============ VECTOR SEARCH IMPLEMENTATION ============
|
|
158
|
+
class VectorSearch:
|
|
159
|
+
"""Vector-based semantic search using sentence transformers"""
|
|
160
|
+
|
|
161
|
+
def __init__(self):
|
|
162
|
+
if not VECTOR_AVAILABLE:
|
|
163
|
+
raise ImportError(
|
|
164
|
+
"Vector search requires sentence-transformers and scikit-learn. "
|
|
165
|
+
"Install with: pip install sentence-transformers scikit-learn"
|
|
166
|
+
)
|
|
167
|
+
# Use lightweight, fast model
|
|
168
|
+
self.model = SentenceTransformer('all-MiniLM-L6-v2')
|
|
169
|
+
self.embeddings = None
|
|
170
|
+
self.documents = None
|
|
171
|
+
|
|
172
|
+
def fit(self, documents):
|
|
173
|
+
"""Encode documents into vectors"""
|
|
174
|
+
self.documents = documents
|
|
175
|
+
if len(documents) == 0:
|
|
176
|
+
self.embeddings = np.array([])
|
|
177
|
+
return
|
|
178
|
+
# Encode without progress bar for cleaner output
|
|
179
|
+
self.embeddings = self.model.encode(documents, show_progress_bar=False, convert_to_numpy=True)
|
|
180
|
+
|
|
181
|
+
def search(self, query, top_k=3):
|
|
182
|
+
"""Search using cosine similarity"""
|
|
183
|
+
if self.embeddings is None or len(self.embeddings) == 0:
|
|
184
|
+
return []
|
|
185
|
+
|
|
186
|
+
# Encode query
|
|
187
|
+
query_embedding = self.model.encode([query], show_progress_bar=False, convert_to_numpy=True)
|
|
188
|
+
|
|
189
|
+
# Calculate cosine similarity
|
|
190
|
+
similarities = cosine_similarity(query_embedding, self.embeddings)[0]
|
|
191
|
+
|
|
192
|
+
# Get top k indices
|
|
193
|
+
top_indices = np.argsort(similarities)[::-1][:top_k]
|
|
194
|
+
|
|
195
|
+
# Return (index, score) tuples
|
|
196
|
+
return [(int(idx), float(similarities[idx])) for idx in top_indices if similarities[idx] > 0]
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# ============ SEARCH FUNCTIONS ============
|
|
200
|
+
def _load_csv(filepath):
|
|
201
|
+
"""Load CSV and return list of dicts"""
|
|
202
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
203
|
+
return list(csv.DictReader(f))
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _search_csv(filepath, search_cols, output_cols, query, max_results, mode='bm25'):
|
|
207
|
+
"""Core search function using BM25, Vector, or Hybrid"""
|
|
208
|
+
if not filepath.exists():
|
|
209
|
+
return []
|
|
210
|
+
|
|
211
|
+
data = _load_csv(filepath)
|
|
212
|
+
|
|
213
|
+
# Build documents from search columns
|
|
214
|
+
documents = [" ".join(str(row.get(col, "")) for col in search_cols) for row in data]
|
|
215
|
+
|
|
216
|
+
if len(documents) == 0:
|
|
217
|
+
return []
|
|
218
|
+
|
|
219
|
+
# Choose search mode
|
|
220
|
+
if mode == 'bm25' or (mode in ['vector', 'hybrid'] and not VECTOR_AVAILABLE):
|
|
221
|
+
# BM25 search (default or fallback)
|
|
222
|
+
bm25 = BM25()
|
|
223
|
+
bm25.fit(documents)
|
|
224
|
+
ranked = bm25.score(query)
|
|
225
|
+
|
|
226
|
+
# Get top results with score > 0
|
|
227
|
+
results = []
|
|
228
|
+
for idx, score in ranked[:max_results]:
|
|
229
|
+
if score > 0:
|
|
230
|
+
row = data[idx]
|
|
231
|
+
results.append({col: row.get(col, "") for col in output_cols if col in row})
|
|
232
|
+
|
|
233
|
+
return results
|
|
234
|
+
|
|
235
|
+
elif mode == 'vector':
|
|
236
|
+
# Vector search
|
|
237
|
+
vector_search = VectorSearch()
|
|
238
|
+
vector_search.fit(documents)
|
|
239
|
+
ranked = vector_search.search(query, top_k=max_results)
|
|
240
|
+
|
|
241
|
+
results = []
|
|
242
|
+
for idx, score in ranked:
|
|
243
|
+
row = data[idx]
|
|
244
|
+
results.append({col: row.get(col, "") for col in output_cols if col in row})
|
|
245
|
+
|
|
246
|
+
return results
|
|
247
|
+
|
|
248
|
+
elif mode == 'hybrid':
|
|
249
|
+
# Hybrid search: combine BM25 + Vector
|
|
250
|
+
# Get more results from each to combine
|
|
251
|
+
search_count = max_results * 2
|
|
252
|
+
|
|
253
|
+
# BM25 results
|
|
254
|
+
bm25 = BM25()
|
|
255
|
+
bm25.fit(documents)
|
|
256
|
+
bm25_ranked = bm25.score(query)
|
|
257
|
+
bm25_scores = {idx: score for idx, score in bm25_ranked[:search_count] if score > 0}
|
|
258
|
+
|
|
259
|
+
# Vector results
|
|
260
|
+
vector_search = VectorSearch()
|
|
261
|
+
vector_search.fit(documents)
|
|
262
|
+
vector_ranked = vector_search.search(query, top_k=search_count)
|
|
263
|
+
vector_scores = {idx: score for idx, score in vector_ranked}
|
|
264
|
+
|
|
265
|
+
# Normalize scores to 0-1 range
|
|
266
|
+
max_bm25 = max(bm25_scores.values()) if bm25_scores else 1.0
|
|
267
|
+
max_vector = max(vector_scores.values()) if vector_scores else 1.0
|
|
268
|
+
|
|
269
|
+
# Combine scores (alpha = 0.5 for balanced, can be adjusted)
|
|
270
|
+
alpha = 0.5
|
|
271
|
+
combined_scores = {}
|
|
272
|
+
all_indices = set(bm25_scores.keys()) | set(vector_scores.keys())
|
|
273
|
+
|
|
274
|
+
for idx in all_indices:
|
|
275
|
+
bm25_norm = (bm25_scores.get(idx, 0) / max_bm25) if max_bm25 > 0 else 0
|
|
276
|
+
vector_norm = (vector_scores.get(idx, 0) / max_vector) if max_vector > 0 else 0
|
|
277
|
+
combined_scores[idx] = alpha * bm25_norm + (1 - alpha) * vector_norm
|
|
278
|
+
|
|
279
|
+
# Sort by combined score
|
|
280
|
+
sorted_indices = sorted(combined_scores.items(), key=lambda x: x[1], reverse=True)
|
|
281
|
+
|
|
282
|
+
# Get top results
|
|
283
|
+
results = []
|
|
284
|
+
for idx, score in sorted_indices[:max_results]:
|
|
285
|
+
if score > 0:
|
|
286
|
+
row = data[idx]
|
|
287
|
+
results.append({col: row.get(col, "") for col in output_cols if col in row})
|
|
288
|
+
|
|
289
|
+
return results
|
|
290
|
+
|
|
291
|
+
else:
|
|
292
|
+
# Unknown mode, fallback to BM25
|
|
293
|
+
return _search_csv(filepath, search_cols, output_cols, query, max_results, mode='bm25')
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def detect_domain(query):
|
|
297
|
+
"""Auto-detect the most relevant domain from query"""
|
|
298
|
+
query_lower = query.lower()
|
|
299
|
+
|
|
300
|
+
domain_keywords = {
|
|
301
|
+
"color": ["color", "palette", "hex", "#", "rgb"],
|
|
302
|
+
"chart": ["chart", "graph", "visualization", "trend", "bar", "pie", "scatter", "heatmap", "funnel"],
|
|
303
|
+
"landing": ["landing", "page", "cta", "conversion", "hero", "testimonial", "pricing", "section"],
|
|
304
|
+
"product": ["saas", "ecommerce", "e-commerce", "fintech", "healthcare", "gaming", "portfolio", "crypto", "dashboard"],
|
|
305
|
+
"prompt": ["prompt", "css", "implementation", "variable", "checklist", "tailwind"],
|
|
306
|
+
"style": ["style", "design", "ui", "minimalism", "glassmorphism", "neumorphism", "brutalism", "dark mode", "flat", "aurora"],
|
|
307
|
+
"ux": ["ux", "usability", "accessibility", "wcag", "touch", "scroll", "animation", "keyboard", "navigation", "mobile"],
|
|
308
|
+
"typography": ["font", "typography", "heading", "serif", "sans"],
|
|
309
|
+
"pages": ["page", "home", "homepage", "about", "post", "article", "blog", "category", "pricing", "faq", "contact", "product", "shop", "catalog", "details", "single"]
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
scores = {domain: sum(1 for kw in keywords if kw in query_lower) for domain, keywords in domain_keywords.items()}
|
|
313
|
+
best = max(scores, key=scores.get)
|
|
314
|
+
return best if scores[best] > 0 else "style"
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def search(query, domain=None, max_results=MAX_RESULTS, mode='bm25'):
|
|
318
|
+
"""
|
|
319
|
+
Main search function with auto-domain detection
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
query: Search query string
|
|
323
|
+
domain: Domain to search (auto-detected if None)
|
|
324
|
+
max_results: Maximum number of results
|
|
325
|
+
mode: Search mode - 'bm25' (default), 'vector', or 'hybrid'
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Dictionary with search results
|
|
329
|
+
"""
|
|
330
|
+
if domain is None:
|
|
331
|
+
domain = detect_domain(query)
|
|
332
|
+
|
|
333
|
+
config = CSV_CONFIG.get(domain, CSV_CONFIG["style"])
|
|
334
|
+
filepath = DATA_DIR / config["file"]
|
|
335
|
+
|
|
336
|
+
if not filepath.exists():
|
|
337
|
+
return {"error": f"File not found: {filepath}", "domain": domain}
|
|
338
|
+
|
|
339
|
+
# Validate mode
|
|
340
|
+
if mode not in ['bm25', 'vector', 'hybrid']:
|
|
341
|
+
mode = 'bm25'
|
|
342
|
+
|
|
343
|
+
# Fallback to BM25 if vector dependencies not available
|
|
344
|
+
if mode in ['vector', 'hybrid'] and not VECTOR_AVAILABLE:
|
|
345
|
+
mode = 'bm25'
|
|
346
|
+
|
|
347
|
+
results = _search_csv(filepath, config["search_cols"], config["output_cols"], query, max_results, mode)
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
"domain": domain,
|
|
351
|
+
"query": query,
|
|
352
|
+
"file": config["file"],
|
|
353
|
+
"mode": mode,
|
|
354
|
+
"count": len(results),
|
|
355
|
+
"results": results
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def search_stack(query, stack, max_results=MAX_RESULTS, mode='bm25'):
|
|
360
|
+
"""Search stack-specific guidelines"""
|
|
361
|
+
if stack not in STACK_CONFIG:
|
|
362
|
+
return {"error": f"Unknown stack: {stack}. Available: {', '.join(AVAILABLE_STACKS)}"}
|
|
363
|
+
|
|
364
|
+
filepath = DATA_DIR / STACK_CONFIG[stack]["file"]
|
|
365
|
+
|
|
366
|
+
if not filepath.exists():
|
|
367
|
+
return {"error": f"Stack file not found: {filepath}", "stack": stack}
|
|
368
|
+
|
|
369
|
+
# Validate mode and fallback if needed
|
|
370
|
+
if mode not in ['bm25', 'vector', 'hybrid']:
|
|
371
|
+
mode = 'bm25'
|
|
372
|
+
if mode in ['vector', 'hybrid'] and not VECTOR_AVAILABLE:
|
|
373
|
+
mode = 'bm25'
|
|
374
|
+
|
|
375
|
+
results = _search_csv(filepath, _STACK_COLS["search_cols"], _STACK_COLS["output_cols"], query, max_results, mode)
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
"domain": "stack",
|
|
379
|
+
"stack": stack,
|
|
380
|
+
"query": query,
|
|
381
|
+
"file": STACK_CONFIG[stack]["file"],
|
|
382
|
+
"mode": mode,
|
|
383
|
+
"count": len(results),
|
|
384
|
+
"results": results
|
|
385
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
UI/UX Builder Search - BM25, Vector, and Hybrid search engine for UI/UX style guides
|
|
5
|
+
Usage: python search.py "<query>" [--domain <domain>] [--stack <stack>] [--mode <mode>] [--max-results 3]
|
|
6
|
+
|
|
7
|
+
Domains: style, prompt, color, chart, landing, product, ux, typography, pages
|
|
8
|
+
Stacks: html-tailwind, react, nextjs, vue, svelte, swiftui, react-native, flutter
|
|
9
|
+
Modes: bm25 (default), vector, hybrid
|
|
10
|
+
|
|
11
|
+
Note: Vector and hybrid modes require: pip install sentence-transformers scikit-learn
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
from core import CSV_CONFIG, AVAILABLE_STACKS, MAX_RESULTS, search, search_stack, VECTOR_AVAILABLE
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def format_output(result):
|
|
19
|
+
"""Format results for Claude consumption (token-optimized)"""
|
|
20
|
+
if "error" in result:
|
|
21
|
+
return f"Error: {result['error']}"
|
|
22
|
+
|
|
23
|
+
output = []
|
|
24
|
+
mode_info = f" | **Mode:** {result.get('mode', 'bm25')}"
|
|
25
|
+
if result.get("stack"):
|
|
26
|
+
output.append(f"## UI/UX Builder Stack Guidelines")
|
|
27
|
+
output.append(f"**Stack:** {result['stack']} | **Query:** {result['query']}{mode_info}")
|
|
28
|
+
else:
|
|
29
|
+
output.append(f"## UI/UX Builder Search Results")
|
|
30
|
+
output.append(f"**Domain:** {result['domain']} | **Query:** {result['query']}{mode_info}")
|
|
31
|
+
output.append(f"**Source:** {result['file']} | **Found:** {result['count']} results\n")
|
|
32
|
+
|
|
33
|
+
for i, row in enumerate(result['results'], 1):
|
|
34
|
+
output.append(f"### Result {i}")
|
|
35
|
+
for key, value in row.items():
|
|
36
|
+
value_str = str(value)
|
|
37
|
+
if len(value_str) > 300:
|
|
38
|
+
value_str = value_str[:300] + "..."
|
|
39
|
+
output.append(f"- **{key}:** {value_str}")
|
|
40
|
+
output.append("")
|
|
41
|
+
|
|
42
|
+
return "\n".join(output)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
parser = argparse.ArgumentParser(description="UI/UX Builder Search")
|
|
47
|
+
parser.add_argument("query", help="Search query")
|
|
48
|
+
parser.add_argument("--domain", "-d", choices=list(CSV_CONFIG.keys()), help="Search domain")
|
|
49
|
+
parser.add_argument("--stack", "-s", choices=AVAILABLE_STACKS, help="Stack-specific search")
|
|
50
|
+
parser.add_argument("--mode", "-m", choices=['bm25', 'vector', 'hybrid'], default='bm25',
|
|
51
|
+
help="Search mode: bm25 (default, keyword-based), vector (semantic), hybrid (best of both)")
|
|
52
|
+
parser.add_argument("--max-results", "-n", type=int, default=MAX_RESULTS, help="Max results (default: 3)")
|
|
53
|
+
parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
54
|
+
|
|
55
|
+
args = parser.parse_args()
|
|
56
|
+
|
|
57
|
+
# Check if vector mode is requested but not available
|
|
58
|
+
if args.mode in ['vector', 'hybrid'] and not VECTOR_AVAILABLE:
|
|
59
|
+
print("Warning: Vector/Hybrid mode requires sentence-transformers and scikit-learn.")
|
|
60
|
+
print("Falling back to BM25 mode. Install with: pip install sentence-transformers scikit-learn")
|
|
61
|
+
args.mode = 'bm25'
|
|
62
|
+
|
|
63
|
+
# Stack search takes priority
|
|
64
|
+
if args.stack:
|
|
65
|
+
result = search_stack(args.query, args.stack, args.max_results, args.mode)
|
|
66
|
+
else:
|
|
67
|
+
result = search(args.query, args.domain, args.max_results, args.mode)
|
|
68
|
+
|
|
69
|
+
if args.json:
|
|
70
|
+
import json
|
|
71
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
72
|
+
else:
|
|
73
|
+
print(format_output(result))
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Skill: Feature Suggestion (Cross-Cutting)
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
This skill is used to analyze existing project structure and suggest new features that would integrate well with the current codebase. It helps identify opportunities for enhancements and new functionality based on existing patterns and architecture.
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
- When planning new features for a project
|
|
10
|
+
- When identifying gaps in functionality
|
|
11
|
+
- When analyzing project structure for enhancement opportunities
|
|
12
|
+
- When proposing improvements based on existing patterns
|
|
13
|
+
- When suggesting features that align with project architecture
|
|
14
|
+
|
|
15
|
+
## Step-by-Step Process
|
|
16
|
+
|
|
17
|
+
### Step 1: Analyze Project Structure
|
|
18
|
+
- Read and understand `heraspec/project.md`
|
|
19
|
+
- Review existing specs in `heraspec/specs/`
|
|
20
|
+
- Analyze existing changes and their patterns
|
|
21
|
+
- Identify project type and tech stack
|
|
22
|
+
- Map current feature set
|
|
23
|
+
|
|
24
|
+
### Step 2: Identify Integration Points
|
|
25
|
+
- Find logical places for new features
|
|
26
|
+
- Identify patterns in existing code
|
|
27
|
+
- Determine appropriate project structure locations
|
|
28
|
+
- Consider dependencies and relationships
|
|
29
|
+
- Map feature hierarchy
|
|
30
|
+
|
|
31
|
+
### Step 3: Generate Suggestions
|
|
32
|
+
- Create feature suggestions based on analysis
|
|
33
|
+
- Ensure suggestions align with project type
|
|
34
|
+
- Consider existing patterns and conventions
|
|
35
|
+
- Propose features that enhance current functionality
|
|
36
|
+
- Suggest improvements and extensions
|
|
37
|
+
|
|
38
|
+
### Step 4: Organize Suggestions
|
|
39
|
+
- Group suggestions by category/domain
|
|
40
|
+
- Prioritize based on feasibility and value
|
|
41
|
+
- Create suggestion files in appropriate locations
|
|
42
|
+
- Link suggestions to existing features
|
|
43
|
+
- Document integration points
|
|
44
|
+
|
|
45
|
+
### Step 5: Review and Refine
|
|
46
|
+
- Review suggestions for consistency
|
|
47
|
+
- Ensure suggestions follow project conventions
|
|
48
|
+
- Verify integration points are correct
|
|
49
|
+
- Refine based on project requirements
|
|
50
|
+
- Prepare for implementation planning
|
|
51
|
+
|
|
52
|
+
## Required Input
|
|
53
|
+
|
|
54
|
+
- **Project structure**: Understanding of project organization
|
|
55
|
+
- **Existing specs**: Current feature specifications
|
|
56
|
+
- **Project type**: Type of project (plugin, module, app, etc.)
|
|
57
|
+
- **Tech stack**: Technologies used in the project
|
|
58
|
+
- **Domain context**: Business domain and requirements
|
|
59
|
+
|
|
60
|
+
## Expected Output
|
|
61
|
+
|
|
62
|
+
- Feature suggestion files
|
|
63
|
+
- Suggestions organized by category
|
|
64
|
+
- Integration points identified
|
|
65
|
+
- Suggestions linked to existing features
|
|
66
|
+
- Documentation of suggested enhancements
|
|
67
|
+
|
|
68
|
+
## Tone & Rules
|
|
69
|
+
|
|
70
|
+
### Suggestion Quality
|
|
71
|
+
- Suggest features that add value
|
|
72
|
+
- Align with project type and architecture
|
|
73
|
+
- Consider existing patterns and conventions
|
|
74
|
+
- Propose realistic and feasible features
|
|
75
|
+
- Enhance rather than replace existing functionality
|
|
76
|
+
|
|
77
|
+
### Integration
|
|
78
|
+
- Identify appropriate locations for features
|
|
79
|
+
- Consider dependencies and relationships
|
|
80
|
+
- Follow existing project structure
|
|
81
|
+
- Maintain consistency with current patterns
|
|
82
|
+
- Document integration approach
|
|
83
|
+
|
|
84
|
+
### Documentation
|
|
85
|
+
- Clearly describe suggested features
|
|
86
|
+
- Explain integration points
|
|
87
|
+
- Link to related existing features
|
|
88
|
+
- Provide context and rationale
|
|
89
|
+
- Include implementation considerations
|
|
90
|
+
|
|
91
|
+
### Limitations
|
|
92
|
+
- ❌ DO NOT suggest features that conflict with existing architecture
|
|
93
|
+
- ❌ DO NOT ignore project conventions
|
|
94
|
+
- ❌ DO NOT suggest overly complex features
|
|
95
|
+
- ❌ DO NOT duplicate existing functionality
|
|
96
|
+
- ❌ DO NOT suggest features without considering integration
|
|
97
|
+
|
|
98
|
+
## Available Templates
|
|
99
|
+
|
|
100
|
+
- `templates/suggestion-template.md` - Template for feature suggestions
|
|
101
|
+
- `templates/suggestion-category-template.md` - Template for organizing suggestions by category
|
|
102
|
+
|
|
103
|
+
## Available Scripts
|
|
104
|
+
|
|
105
|
+
- `scripts/analyze-project-structure.py` - Analyze project structure for suggestion opportunities
|
|
106
|
+
|
|
107
|
+
## Examples
|
|
108
|
+
|
|
109
|
+
See `examples/` directory for reference:
|
|
110
|
+
- `good-suggestions/` - Well-structured feature suggestions
|
|
111
|
+
- `bad-suggestions/` - Examples to avoid
|
|
112
|
+
|
|
113
|
+
## Links to Other Skills
|
|
114
|
+
|
|
115
|
+
- **documents**: Use to document suggestion rationale
|
|
116
|
+
- **unit-test**: Consider testability when suggesting features
|
|
117
|
+
- **integration-test**: Consider integration testing when suggesting features
|
|
118
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Accessibility Checklist (WCAG 2.1 AA)
|
|
2
|
+
|
|
3
|
+
## Perceivable
|
|
4
|
+
|
|
5
|
+
- [ ] All images have descriptive alt text
|
|
6
|
+
- [ ] Color is not the only means of conveying information
|
|
7
|
+
- [ ] Text contrast ratio meets 4.5:1 for normal text, 3:1 for large text
|
|
8
|
+
- [ ] Content can be resized up to 200% without loss of functionality
|
|
9
|
+
|
|
10
|
+
## Operable
|
|
11
|
+
|
|
12
|
+
- [ ] All functionality available via keyboard
|
|
13
|
+
- [ ] No keyboard traps
|
|
14
|
+
- [ ] Focus indicators visible
|
|
15
|
+
- [ ] Sufficient time limits (if any) can be extended
|
|
16
|
+
- [ ] No content that causes seizures (flashing)
|
|
17
|
+
- [ ] Touch targets at least 44x44px
|
|
18
|
+
|
|
19
|
+
## Understandable
|
|
20
|
+
|
|
21
|
+
- [ ] Page titles are descriptive
|
|
22
|
+
- [ ] Form inputs have labels
|
|
23
|
+
- [ ] Error messages are clear and helpful
|
|
24
|
+
- [ ] Language of page is declared
|
|
25
|
+
- [ ] Navigation is consistent
|
|
26
|
+
|
|
27
|
+
## Robust
|
|
28
|
+
|
|
29
|
+
- [ ] Valid HTML markup
|
|
30
|
+
- [ ] ARIA attributes used correctly (if needed)
|
|
31
|
+
- [ ] Screen reader compatible
|
|
32
|
+
- [ ] Works with assistive technologies
|
|
33
|
+
|
|
34
|
+
## Additional Best Practices
|
|
35
|
+
|
|
36
|
+
- [ ] `prefers-reduced-motion` respected
|
|
37
|
+
- [ ] Skip links for keyboard navigation
|
|
38
|
+
- [ ] Logical tab order
|
|
39
|
+
- [ ] Focus management for modals/dialogs
|
|
40
|
+
|