holycodex 0.2.1
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.md +82 -0
- package/README.md +107 -0
- package/THIRD-PARTY-NOTICES.md +7 -0
- package/bin/holycodex.js +2 -0
- package/marketplace.json +12 -0
- package/package.json +59 -0
- package/plugin/.codex-plugin/plugin.json +29 -0
- package/plugin/.mcp.json +8 -0
- package/plugin/agents/explorer.toml +6 -0
- package/plugin/agents/librarian.toml +6 -0
- package/plugin/agents/worker.toml +6 -0
- package/plugin/hooks/hooks.json +93 -0
- package/plugin/runtime/bootstrap.js +23 -0
- package/plugin/runtime/cli.js +233 -0
- package/plugin/runtime/git-bash.js +359 -0
- package/plugin/runtime/lsp.js +3674 -0
- package/plugin/runtime/rules.js +146 -0
- package/plugin/runtime/src-tRIOClWZ.js +209 -0
- package/plugin/skills/ast-grep/LICENSE +21 -0
- package/plugin/skills/ast-grep/SKILL.md +20 -0
- package/plugin/skills/ast-grep/references/cli.md +3 -0
- package/plugin/skills/ast-grep/references/install.md +3 -0
- package/plugin/skills/ast-grep/references/patterns.md +3 -0
- package/plugin/skills/ast-grep/references/pitfalls.md +3 -0
- package/plugin/skills/ast-grep/references/recipes.md +9 -0
- package/plugin/skills/ast-grep/references/sgconfig.md +3 -0
- package/plugin/skills/ast-grep/references/yaml-rules.md +3 -0
- package/plugin/skills/caveman/LICENSE +21 -0
- package/plugin/skills/caveman/SKILL.md +84 -0
- package/plugin/skills/comment-checker/SKILL.md +16 -0
- package/plugin/skills/compress/SKILL.md +26 -0
- package/plugin/skills/debugging/SKILL.md +23 -0
- package/plugin/skills/debugging/references/runtimes/README.md +9 -0
- package/plugin/skills/debugging/references/tools/README.md +8 -0
- package/plugin/skills/define-goal/LICENSE.txt +201 -0
- package/plugin/skills/define-goal/SKILL.md +36 -0
- package/plugin/skills/frontend/ATTRIBUTION.md +218 -0
- package/plugin/skills/frontend/LICENSE-Apache-2.0.txt +201 -0
- package/plugin/skills/frontend/SKILL.md +42 -0
- package/plugin/skills/frontend/references/perfection/README.md +5 -0
- package/plugin/skills/frontend/references/perfection/react-perf-tooling.md +3 -0
- package/plugin/skills/frontend/references/ui-ux-db/README.md +5 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/charts.csv +26 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/colors.csv +162 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/icons.csv +106 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/landing.csv +35 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/products.csv +162 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/react-performance.csv +45 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/astro.csv +54 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/flutter.csv +53 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/html-tailwind.csv +56 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/jetpack-compose.csv +53 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/nextjs.csv +53 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/nuxt-ui.csv +51 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/nuxtjs.csv +59 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/react-native.csv +52 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/react.csv +54 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/shadcn.csv +61 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/svelte.csv +54 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/swiftui.csv +51 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/stacks/vue.csv +50 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/styles.csv +85 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/typography.csv +74 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/ui-reasoning.csv +162 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/ux-guidelines.csv +100 -0
- package/plugin/skills/frontend/references/ui-ux-db/data/web-interface.csv +31 -0
- package/plugin/skills/frontend/references/ui-ux-db/scripts/core.py +262 -0
- package/plugin/skills/frontend/references/ui-ux-db/scripts/design_system.py +1148 -0
- package/plugin/skills/frontend/references/ui-ux-db/scripts/search.py +114 -0
- package/plugin/skills/handoff/SKILL.md +10 -0
- package/plugin/skills/lsp/SKILL.md +35 -0
- package/plugin/skills/lsp-setup/SKILL.md +18 -0
- package/plugin/skills/lsp-setup/scripts/detect-lsp.ts +233 -0
- package/plugin/skills/lsp-setup/scripts/lsp-server-table.ts +188 -0
- package/plugin/skills/lsp-setup/scripts/tsconfig.json +17 -0
- package/plugin/skills/lsp-setup/scripts/verify-lsp.ts +166 -0
- package/plugin/skills/plan/SKILL.md +14 -0
- package/plugin/skills/plan-review/SKILL.md +12 -0
- package/plugin/skills/programming/SKILL.md +35 -0
- package/plugin/skills/programming/references/code-smells.md +3 -0
- package/plugin/skills/programming/references/go/README.md +3 -0
- package/plugin/skills/programming/references/logging.md +3 -0
- package/plugin/skills/programming/references/python/README.md +3 -0
- package/plugin/skills/programming/references/rust/README.md +3 -0
- package/plugin/skills/programming/references/rust-ub/README.md +3 -0
- package/plugin/skills/programming/references/typescript/README.md +3 -0
- package/plugin/skills/refactor/SKILL.md +21 -0
- package/plugin/skills/remove-ai-slops/SKILL.md +20 -0
- package/plugin/skills/rules/SKILL.md +16 -0
- package/plugin/skills/security-research/SKILL.md +38 -0
- package/plugin/skills/tdd/SKILL.md +24 -0
- package/plugin/skills/tdd/mocking.md +60 -0
- package/plugin/skills/tdd/tests.md +77 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
No,Category,Issue,Keywords,Platform,Description,Do,Don't,Code Example Good,Code Example Bad,Severity
|
|
2
|
+
1,Accessibility,Icon Button Labels,icon button accessibilityLabel,iOS/Android/React Native,Icon-only buttons must expose an accessible label,Set accessibilityLabel or label prop on icon buttons,Icon buttons without accessible names,"<Pressable accessibilityLabel=""Close""><XIcon /></Pressable>","<Pressable><XIcon /></Pressable>",Critical
|
|
3
|
+
2,Accessibility,Form Control Labels,form input label accessibilityLabel,iOS/Android/React Native,All inputs must have a visible label and an accessibility label,Pair Text label with input and set accessibilityLabel,Inputs with placeholder only,"<View><Text>Email</Text><TextInput accessibilityLabel=""Email address"" /></View>","<TextInput placeholder=""Email"" /></View>",Critical
|
|
4
|
+
3,Accessibility,Role & Traits,accessibilityRole accessibilityTraits,iOS/Android/React Native,Interactive elements must expose correct roles/traits,Use accessibilityRole/button/link/checkbox etc.,Rely on generic views with no roles,"<Pressable accessibilityRole=""button"">Submit</Pressable>","<View onTouchStart={submit}>Submit</View>",High
|
|
5
|
+
4,Accessibility,Dynamic Updates,accessibilityLiveRegion announce,iOS/Android/React Native,Async status updates should be announced to screen readers,Use accessibilityLiveRegion or announceForAccessibility,Update text silently with no announcement,"<Text accessibilityLiveRegion=""polite"">{status}</Text>","<Text>{status}</Text>",Medium
|
|
6
|
+
5,Accessibility,Decorative Icons,accessible={false} importantForAccessibility,iOS/Android/React Native,Decorative icons should be hidden from screen readers,Mark decorative icons as not accessible,Have screen reader read every icon,"<Icon accessible={false} importantForAccessibility=""no"" />","<Icon />",Medium
|
|
7
|
+
6,Touch,Touch Target Size,touch 44x44 hitSlop,iOS/Android/React Native,Primary touch targets must be at least 44x44pt,Increase hitSlop or padding to meet minimum,Small icons with tiny touch area,"<Pressable hitSlop={10}><Icon /></Pressable>","<Pressable><Icon style={{ width: 16, height: 16 }} /></Pressable>",Critical
|
|
8
|
+
7,Touch,Touch Spacing,touch spacing gap 8px,iOS/Android/React Native,Adjacent touch targets need enough spacing,Keep at least 8dp spacing between touchables,Cluster many buttons with no gap,"<View style={{ gap: 8 }}><Button ... /><Button ... /></View>","<View><Button ... /><Button ... /></View>",Medium
|
|
9
|
+
8,Touch,Gesture Conflicts,scroll swipe back gesture,iOS/Android/React Native,Custom gestures must not break system scroll/back,Reserve horizontal swipes for carousels,Full-screen custom swipe conflicting with back,"HorizontalPager inside vertical ScrollView","PanResponder on full screen blocking back",High
|
|
10
|
+
9,Navigation,Back Behavior,back handler navigation stack,iOS/Android/React Native,Back navigation should be predictable and preserve state,Use navigation.goBack and keep screen state,Reset stack or exit app unexpectedly,onPress={() => navigation.goBack()},"BackHandler.exitApp() on first press",Critical
|
|
11
|
+
10,Navigation,Bottom Tabs,tab bar max items,iOS/Android/React Native,Bottom tab bar should have at most 5 primary items,Use 3–5 tabs and move extras to More/Settings,Overloaded tab bar with many icons,Home/Explore/Profile/Settings,"Home/Explore/Shop/Cart/Profile/Settings/More",Medium
|
|
12
|
+
11,Navigation,Modal Escape,modal dismiss close affordance,iOS/Android/React Native,Modals/sheets must have clear close actions,Provide close button and swipe-down where platform expects,Trapping users in modal with no obvious exit,"<Modal><Button title=""Close"" onPress={onClose} /></Modal>","<Modal><View>{children}</View></Modal>",High
|
|
13
|
+
12,State,Preserve Screen State,navigation preserve state,iOS/Android/React Native,Returning to a screen should restore its scroll and form state,Keep components mounted or persist state,Reset list scroll and form inputs on every visit,"<Tab.Navigator screenOptions={{ unmountOnBlur: false }}>","<Tab.Screen options={{ unmountOnBlur: true }} />",Medium
|
|
14
|
+
13,Feedback,Loading Indicators,activity indicator skeleton,iOS/Android/React Native,Show visible feedback during network operations,Use ActivityIndicator or skeleton for >300ms operations,Leave button and screen frozen,"{loading ? <ActivityIndicator /> : <Button title=""Save"" />}", "<Button title=""Save"" onPress={submit} /> // no loading",High
|
|
15
|
+
14,Feedback,Success Feedback,toast checkmark banner,iOS/Android/React Native,Confirm successful actions with brief feedback,Show toast/checkmark or banner,Complete actions silently with no confirmation,"showToast('Saved successfully')","// silently update state only",Medium
|
|
16
|
+
15,Feedback,Error Feedback,inline error banner,iOS/Android/React Native,Show clear error messages near the problem,input-level error + summary banner,Only change border color with no explanation,"<TextInput ... /><Text style={{color:'red'}}>{error}</Text>","<TextInput style={{borderColor:'red'}} />",High
|
|
17
|
+
16,Forms,Inline Validation,onBlur validation,iOS/Android/React Native,Validate inputs on blur or submit with clear messaging,Validate onBlur and onSubmit,Validate on every keystroke causing jank,"onBlur={() => validateEmail(value)}","onChangeText={v => validateEmail(v)} // every char",Medium
|
|
18
|
+
17,Forms,Keyboard Type,keyboardType returnKeyType,iOS/Android/React Native,Use appropriate keyboardType and returnKeyType,Match email/tel/number/search types,Use default keyboard for all inputs,"<TextInput keyboardType=""email-address"" />","<TextInput keyboardType=""default"" />",Medium
|
|
19
|
+
18,Forms,Auto Focus & Next,autoFocus blurOnSubmit onSubmitEditing,iOS/Android/React Native,Guide users through form fields with Next/Done flows,Use onSubmitEditing to focus next input,Force users to tap each field manually,"onSubmitEditing={() => nextRef.current?.focus()}","// no onSubmitEditing, manual tap only",Low
|
|
20
|
+
19,Forms,Password Visibility,secureTextEntry toggle,iOS/Android/React Native,Allow toggling password visibility securely,Provide Show/Hide icon toggling secureTextEntry,Force users to type blind with no option,"<TextInput secureTextEntry={secure} /><Icon onPress={toggle} />","<TextInput secureTextEntry /> // no toggle",Medium
|
|
21
|
+
20,Performance,Virtualize Long Lists,FlatList SectionList virtualization,iOS/Android/React Native,Use FlatList/SectionList for lists over ~50 items,Use keyExtractor and initialNumToRender appropriately,Render hundreds of items with ScrollView,"<FlatList data={items} renderItem={...} />","<ScrollView>{items.map(renderItem)}</ScrollView>",High
|
|
22
|
+
21,Performance,Image Size & Cache,Image resize cache,iOS/Android/React Native,Use correctly sized and cached images,Use Image component with proper resizeMode and caching,Load full-resolution images everywhere,"<Image source={{uri}} resizeMode=""cover"" />","<Image source={require('4k.png')} /> // small avatar",Medium
|
|
23
|
+
22,Performance,Debounce High-Freq Events,debounce scroll search,iOS/Android/React Native,Debounce scroll/search callbacks to avoid jank,Wrap handlers with debounce/throttle,Run heavy logic on every event,"onScroll={debouncedHandleScroll}","onScroll={handleScrollHeavy}",Medium
|
|
24
|
+
23,Animation,Duration & Easing,animation duration easing,iOS/Android/React Native,Micro-interactions should be 150–300ms with native-like easing,Use ease-out for enter/ease-in for exit,Use long or linear animations for core UI,"Animated.timing(..., { duration: 200, easing: Easing.out(Easing.quad) })","Animated.timing(..., { duration: 800, easing: Easing.linear })",Medium
|
|
25
|
+
24,Animation,Respect Reduced Motion,reduced motion accessibility,iOS/Android/React Native,Respect OS reduced-motion accessibility setting,Check reduceMotionEnabled and simplify animations,Ignore user motion preferences,"if (reduceMotionEnabled) skipAnimation()","Always run complex parallax animations",Critical
|
|
26
|
+
25,Animation,Limited Continuous Motion,loop animation loader,iOS/Android/React Native,Reserve infinite animations for loaders and live data,Use looping only where necessary,Keep decorative elements looping forever,"Animated.loop(loaderAnim) for ActivityIndicator","Animated.loop(bounceAnim) on background icons",Medium
|
|
27
|
+
26,Typography,Base Font Size,fontScale dynamic type,iOS/Android/React Native,Body text must be readable and support Dynamic Type,Use platform fontScale and at least 14–16pt base,Render critical text below 12pt,"<Text style={{ fontSize: 16 }}>Body</Text>","<Text style={{ fontSize: 10 }}>Body</Text>",High
|
|
28
|
+
27,Typography,Dynamic Type Support,allowFontScaling adjustsFontSizeToFit,iOS/Android/React Native,Support system text scaling without breaking layout,Set allowFontScaling and test large text,Disable scaling on all text globally,"<Text allowFontScaling>{label}</Text>","<Text allowFontScaling={false}>{label}</Text>",High
|
|
29
|
+
28,Safe Areas,Safe Area Insets,safe area insets notch gesture,iOS/Android/React Native,Content must not overlap notches/gesture bars,Wrap screens in SafeAreaView or apply insets,Place tappable content under system bars,"<SafeAreaView style={{ flex: 1 }}><Screen /></SafeAreaView>","<View style={{ flex: 1 }}><Screen /></View>",High
|
|
30
|
+
29,Theming,Light/Dark Contrast,dark mode contrast tokens,iOS/Android/React Native,Ensure sufficient contrast in both light and dark themes,Use semantic tokens and test both themes,Reuse light-theme grays directly in dark mode,"colors.textPrimaryDark = '#F9FAFB'","colors.textPrimaryDark = '#9CA3AF' on '#111827'",High
|
|
31
|
+
30,Anti-Pattern,No Gesture-Only Actions,gesture only hidden controls,iOS/Android/React Native,Don't rely solely on hidden gestures for core actions,Provide visible buttons in addition to gestures,Rely on swipe/shake only with no UI affordance,"Swipe to delete + visible Delete button","Only shake device to undo with no UI",Critical
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
UI/UX Pro Max 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
|
+
# ============ CONFIGURATION ============
|
|
14
|
+
DATA_DIR = Path(__file__).parent.parent / "data"
|
|
15
|
+
MAX_RESULTS = 3
|
|
16
|
+
|
|
17
|
+
CSV_CONFIG = {
|
|
18
|
+
"style": {
|
|
19
|
+
"file": "styles.csv",
|
|
20
|
+
"search_cols": ["Style Category", "Keywords", "Best For", "Type", "AI Prompt Keywords"],
|
|
21
|
+
"output_cols": ["Style Category", "Type", "Keywords", "Primary Colors", "Effects & Animation", "Best For", "Light Mode ✓", "Dark Mode ✓", "Performance", "Accessibility", "Framework Compatibility", "Complexity", "AI Prompt Keywords", "CSS/Technical Keywords", "Implementation Checklist", "Design System Variables"]
|
|
22
|
+
},
|
|
23
|
+
"color": {
|
|
24
|
+
"file": "colors.csv",
|
|
25
|
+
"search_cols": ["Product Type", "Notes"],
|
|
26
|
+
"output_cols": ["Product Type", "Primary", "On Primary", "Secondary", "On Secondary", "Accent", "On Accent", "Background", "Foreground", "Card", "Card Foreground", "Muted", "Muted Foreground", "Border", "Destructive", "On Destructive", "Ring", "Notes"]
|
|
27
|
+
},
|
|
28
|
+
"chart": {
|
|
29
|
+
"file": "charts.csv",
|
|
30
|
+
"search_cols": ["Data Type", "Keywords", "Best Chart Type", "When to Use", "When NOT to Use", "Accessibility Notes"],
|
|
31
|
+
"output_cols": ["Data Type", "Keywords", "Best Chart Type", "Secondary Options", "When to Use", "When NOT to Use", "Data Volume Threshold", "Color Guidance", "Accessibility Grade", "Accessibility Notes", "A11y Fallback", "Library Recommendation", "Interactive Level"]
|
|
32
|
+
},
|
|
33
|
+
"landing": {
|
|
34
|
+
"file": "landing.csv",
|
|
35
|
+
"search_cols": ["Pattern Name", "Keywords", "Conversion Optimization", "Section Order"],
|
|
36
|
+
"output_cols": ["Pattern Name", "Keywords", "Section Order", "Primary CTA Placement", "Color Strategy", "Conversion Optimization"]
|
|
37
|
+
},
|
|
38
|
+
"product": {
|
|
39
|
+
"file": "products.csv",
|
|
40
|
+
"search_cols": ["Product Type", "Keywords", "Primary Style Recommendation", "Key Considerations"],
|
|
41
|
+
"output_cols": ["Product Type", "Keywords", "Primary Style Recommendation", "Secondary Styles", "Landing Page Pattern", "Dashboard Style (if applicable)", "Color Palette Focus"]
|
|
42
|
+
},
|
|
43
|
+
"ux": {
|
|
44
|
+
"file": "ux-guidelines.csv",
|
|
45
|
+
"search_cols": ["Category", "Issue", "Description", "Platform"],
|
|
46
|
+
"output_cols": ["Category", "Issue", "Platform", "Description", "Do", "Don't", "Code Example Good", "Code Example Bad", "Severity"]
|
|
47
|
+
},
|
|
48
|
+
"typography": {
|
|
49
|
+
"file": "typography.csv",
|
|
50
|
+
"search_cols": ["Font Pairing Name", "Category", "Mood/Style Keywords", "Best For", "Heading Font", "Body Font"],
|
|
51
|
+
"output_cols": ["Font Pairing Name", "Category", "Heading Font", "Body Font", "Mood/Style Keywords", "Best For", "Google Fonts URL", "CSS Import", "Tailwind Config", "Notes"]
|
|
52
|
+
},
|
|
53
|
+
"icons": {
|
|
54
|
+
"file": "icons.csv",
|
|
55
|
+
"search_cols": ["Category", "Icon Name", "Keywords", "Best For"],
|
|
56
|
+
"output_cols": ["Category", "Icon Name", "Keywords", "Library", "Import Code", "Usage", "Best For", "Style"]
|
|
57
|
+
},
|
|
58
|
+
"react": {
|
|
59
|
+
"file": "react-performance.csv",
|
|
60
|
+
"search_cols": ["Category", "Issue", "Keywords", "Description"],
|
|
61
|
+
"output_cols": ["Category", "Issue", "Platform", "Description", "Do", "Don't", "Code Example Good", "Code Example Bad", "Severity"]
|
|
62
|
+
},
|
|
63
|
+
"web": {
|
|
64
|
+
"file": "app-interface.csv",
|
|
65
|
+
"search_cols": ["Category", "Issue", "Keywords", "Description"],
|
|
66
|
+
"output_cols": ["Category", "Issue", "Platform", "Description", "Do", "Don't", "Code Example Good", "Code Example Bad", "Severity"]
|
|
67
|
+
},
|
|
68
|
+
"google-fonts": {
|
|
69
|
+
"file": "google-fonts.csv",
|
|
70
|
+
"search_cols": ["Family", "Category", "Stroke", "Classifications", "Keywords", "Subsets", "Designers"],
|
|
71
|
+
"output_cols": ["Family", "Category", "Stroke", "Classifications", "Styles", "Variable Axes", "Subsets", "Designers", "Popularity Rank", "Google Fonts URL"]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
STACK_CONFIG = {
|
|
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
|
+
"astro": {"file": "stacks/astro.csv"},
|
|
81
|
+
"swiftui": {"file": "stacks/swiftui.csv"},
|
|
82
|
+
"react-native": {"file": "stacks/react-native.csv"},
|
|
83
|
+
"flutter": {"file": "stacks/flutter.csv"},
|
|
84
|
+
"nuxtjs": {"file": "stacks/nuxtjs.csv"},
|
|
85
|
+
"nuxt-ui": {"file": "stacks/nuxt-ui.csv"},
|
|
86
|
+
"html-tailwind": {"file": "stacks/html-tailwind.csv"},
|
|
87
|
+
"shadcn": {"file": "stacks/shadcn.csv"},
|
|
88
|
+
"jetpack-compose": {"file": "stacks/jetpack-compose.csv"},
|
|
89
|
+
"threejs": {"file": "stacks/threejs.csv"},
|
|
90
|
+
"angular": {"file": "stacks/angular.csv"},
|
|
91
|
+
"laravel": {"file": "stacks/laravel.csv"},
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# Common columns for all stacks
|
|
95
|
+
_STACK_COLS = {
|
|
96
|
+
"search_cols": ["Category", "Guideline", "Description", "Do", "Don't"],
|
|
97
|
+
"output_cols": ["Category", "Guideline", "Description", "Do", "Don't", "Code Good", "Code Bad", "Severity", "Docs URL"]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
AVAILABLE_STACKS = list(STACK_CONFIG.keys())
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# ============ BM25 IMPLEMENTATION ============
|
|
104
|
+
class BM25:
|
|
105
|
+
"""BM25 ranking algorithm for text search"""
|
|
106
|
+
|
|
107
|
+
def __init__(self, k1=1.5, b=0.75):
|
|
108
|
+
self.k1 = k1
|
|
109
|
+
self.b = b
|
|
110
|
+
self.corpus = []
|
|
111
|
+
self.doc_lengths = []
|
|
112
|
+
self.avgdl = 0
|
|
113
|
+
self.idf = {}
|
|
114
|
+
self.doc_freqs = defaultdict(int)
|
|
115
|
+
self.N = 0
|
|
116
|
+
|
|
117
|
+
def tokenize(self, text):
|
|
118
|
+
"""Lowercase, split, remove punctuation, filter short words"""
|
|
119
|
+
text = re.sub(r'[^\w\s]', ' ', str(text).lower())
|
|
120
|
+
return [w for w in text.split() if len(w) > 2]
|
|
121
|
+
|
|
122
|
+
def fit(self, documents):
|
|
123
|
+
"""Build BM25 index from documents"""
|
|
124
|
+
self.corpus = [self.tokenize(doc) for doc in documents]
|
|
125
|
+
self.N = len(self.corpus)
|
|
126
|
+
if self.N == 0:
|
|
127
|
+
return
|
|
128
|
+
self.doc_lengths = [len(doc) for doc in self.corpus]
|
|
129
|
+
self.avgdl = sum(self.doc_lengths) / self.N
|
|
130
|
+
|
|
131
|
+
for doc in self.corpus:
|
|
132
|
+
seen = set()
|
|
133
|
+
for word in doc:
|
|
134
|
+
if word not in seen:
|
|
135
|
+
self.doc_freqs[word] += 1
|
|
136
|
+
seen.add(word)
|
|
137
|
+
|
|
138
|
+
for word, freq in self.doc_freqs.items():
|
|
139
|
+
self.idf[word] = log((self.N - freq + 0.5) / (freq + 0.5) + 1)
|
|
140
|
+
|
|
141
|
+
def score(self, query):
|
|
142
|
+
"""Score all documents against query"""
|
|
143
|
+
query_tokens = self.tokenize(query)
|
|
144
|
+
scores = []
|
|
145
|
+
|
|
146
|
+
for idx, doc in enumerate(self.corpus):
|
|
147
|
+
score = 0
|
|
148
|
+
doc_len = self.doc_lengths[idx]
|
|
149
|
+
term_freqs = defaultdict(int)
|
|
150
|
+
for word in doc:
|
|
151
|
+
term_freqs[word] += 1
|
|
152
|
+
|
|
153
|
+
for token in query_tokens:
|
|
154
|
+
if token in self.idf:
|
|
155
|
+
tf = term_freqs[token]
|
|
156
|
+
idf = self.idf[token]
|
|
157
|
+
numerator = tf * (self.k1 + 1)
|
|
158
|
+
denominator = tf + self.k1 * (1 - self.b + self.b * doc_len / self.avgdl)
|
|
159
|
+
score += idf * numerator / denominator
|
|
160
|
+
|
|
161
|
+
scores.append((idx, score))
|
|
162
|
+
|
|
163
|
+
return sorted(scores, key=lambda x: x[1], reverse=True)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# ============ SEARCH FUNCTIONS ============
|
|
167
|
+
def _load_csv(filepath):
|
|
168
|
+
"""Load CSV and return list of dicts"""
|
|
169
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
170
|
+
return list(csv.DictReader(f))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _search_csv(filepath, search_cols, output_cols, query, max_results):
|
|
174
|
+
"""Core search function using BM25"""
|
|
175
|
+
if not filepath.exists():
|
|
176
|
+
return []
|
|
177
|
+
|
|
178
|
+
data = _load_csv(filepath)
|
|
179
|
+
|
|
180
|
+
# Build documents from search columns
|
|
181
|
+
documents = [" ".join(str(row.get(col, "")) for col in search_cols) for row in data]
|
|
182
|
+
|
|
183
|
+
# BM25 search
|
|
184
|
+
bm25 = BM25()
|
|
185
|
+
bm25.fit(documents)
|
|
186
|
+
ranked = bm25.score(query)
|
|
187
|
+
|
|
188
|
+
# Get top results with score > 0
|
|
189
|
+
results = []
|
|
190
|
+
for idx, score in ranked[:max_results]:
|
|
191
|
+
if score > 0:
|
|
192
|
+
row = data[idx]
|
|
193
|
+
results.append({col: row.get(col, "") for col in output_cols if col in row})
|
|
194
|
+
|
|
195
|
+
return results
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def detect_domain(query):
|
|
199
|
+
"""Auto-detect the most relevant domain from query"""
|
|
200
|
+
query_lower = query.lower()
|
|
201
|
+
|
|
202
|
+
domain_keywords = {
|
|
203
|
+
"color": ["color", "palette", "hex", "#", "rgb", "token", "semantic", "accent", "destructive", "muted", "foreground"],
|
|
204
|
+
"chart": ["chart", "graph", "visualization", "trend", "bar", "pie", "scatter", "heatmap", "funnel"],
|
|
205
|
+
"landing": ["landing", "page", "cta", "conversion", "hero", "testimonial", "pricing", "section"],
|
|
206
|
+
"product": ["saas", "ecommerce", "e-commerce", "fintech", "healthcare", "gaming", "portfolio", "crypto", "dashboard", "fitness", "restaurant", "hotel", "travel", "music", "education", "learning", "legal", "insurance", "medical", "beauty", "pharmacy", "dental", "pet", "dating", "wedding", "recipe", "delivery", "ride", "booking", "calendar", "timer", "tracker", "diary", "note", "chat", "messenger", "crm", "invoice", "parking", "transit", "vpn", "alarm", "weather", "sleep", "meditation", "fasting", "habit", "grocery", "meme", "wardrobe", "plant care", "reading", "flashcard", "puzzle", "trivia", "arcade", "photography", "streaming", "podcast", "newsletter", "marketplace", "freelancer", "coworking", "airline", "museum", "theater", "church", "non-profit", "charity", "kindergarten", "daycare", "senior care", "veterinary", "florist", "bakery", "brewery", "construction", "automotive", "real estate", "logistics", "agriculture", "coding bootcamp"],
|
|
207
|
+
"style": ["style", "design", "ui", "minimalism", "glassmorphism", "neumorphism", "brutalism", "dark mode", "flat", "aurora", "prompt", "css", "implementation", "variable", "checklist", "tailwind"],
|
|
208
|
+
"ux": ["ux", "usability", "accessibility", "wcag", "touch", "scroll", "animation", "keyboard", "navigation", "mobile"],
|
|
209
|
+
"typography": ["font pairing", "typography pairing", "heading font", "body font"],
|
|
210
|
+
"google-fonts": ["google font", "font family", "font weight", "font style", "variable font", "noto", "font for", "find font", "font subset", "font language", "monospace font", "serif font", "sans serif font", "display font", "handwriting font", "font", "typography", "serif", "sans"],
|
|
211
|
+
"icons": ["icon", "icons", "lucide", "heroicons", "symbol", "glyph", "pictogram", "svg icon"],
|
|
212
|
+
"react": ["react", "next.js", "nextjs", "suspense", "memo", "usecallback", "useeffect", "rerender", "bundle", "waterfall", "barrel", "dynamic import", "rsc", "server component"],
|
|
213
|
+
"web": ["aria", "focus", "outline", "semantic", "virtualize", "autocomplete", "form", "input type", "preconnect"]
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
scores = {domain: sum(1 for kw in keywords if re.search(r'\b' + re.escape(kw) + r'\b', query_lower)) for domain, keywords in domain_keywords.items()}
|
|
217
|
+
best = max(scores, key=scores.get)
|
|
218
|
+
return best if scores[best] > 0 else "style"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def search(query, domain=None, max_results=MAX_RESULTS):
|
|
222
|
+
"""Main search function with auto-domain detection"""
|
|
223
|
+
if domain is None:
|
|
224
|
+
domain = detect_domain(query)
|
|
225
|
+
|
|
226
|
+
config = CSV_CONFIG.get(domain, CSV_CONFIG["style"])
|
|
227
|
+
filepath = DATA_DIR / config["file"]
|
|
228
|
+
|
|
229
|
+
if not filepath.exists():
|
|
230
|
+
return {"error": f"File not found: {filepath}", "domain": domain}
|
|
231
|
+
|
|
232
|
+
results = _search_csv(filepath, config["search_cols"], config["output_cols"], query, max_results)
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
"domain": domain,
|
|
236
|
+
"query": query,
|
|
237
|
+
"file": config["file"],
|
|
238
|
+
"count": len(results),
|
|
239
|
+
"results": results
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def search_stack(query, stack, max_results=MAX_RESULTS):
|
|
244
|
+
"""Search stack-specific guidelines"""
|
|
245
|
+
if stack not in STACK_CONFIG:
|
|
246
|
+
return {"error": f"Unknown stack: {stack}. Available: {', '.join(AVAILABLE_STACKS)}"}
|
|
247
|
+
|
|
248
|
+
filepath = DATA_DIR / STACK_CONFIG[stack]["file"]
|
|
249
|
+
|
|
250
|
+
if not filepath.exists():
|
|
251
|
+
return {"error": f"Stack file not found: {filepath}", "stack": stack}
|
|
252
|
+
|
|
253
|
+
results = _search_csv(filepath, _STACK_COLS["search_cols"], _STACK_COLS["output_cols"], query, max_results)
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
"domain": "stack",
|
|
257
|
+
"stack": stack,
|
|
258
|
+
"query": query,
|
|
259
|
+
"file": STACK_CONFIG[stack]["file"],
|
|
260
|
+
"count": len(results),
|
|
261
|
+
"results": results
|
|
262
|
+
}
|