@lugom.io/hefesto 0.3.0 → 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/agents/hefesto-argos.md +51 -237
- package/agents/hefesto-athena.md +59 -339
- package/agents/hefesto-hermes.md +39 -71
- package/bin/install.js +105 -69
- package/hooks/hefesto-check-update.cjs +32 -11
- package/hooks/hefesto-statusline.cjs +8 -17
- package/hooks/hefesto-workflow.cjs +68 -0
- package/package.json +12 -2
- package/skills/hefesto-context/SKILL.md +59 -26
- package/skills/hefesto-debug/SKILL.md +54 -0
- package/skills/hefesto-design/SKILL.md +133 -143
- package/skills/hefesto-execute/SKILL.md +133 -0
- package/skills/hefesto-init/SKILL.md +94 -59
- package/skills/hefesto-init/references/api.md +116 -0
- package/skills/hefesto-init/references/cli.md +91 -0
- package/skills/hefesto-init/references/mobile.md +69 -0
- package/skills/hefesto-init/references/web.md +246 -0
- package/skills/hefesto-new-feature/SKILL.md +75 -41
- package/skills/hefesto-security/SKILL.md +89 -0
- package/skills/hefesto-security/references/boundaries-and-bypasses.md +152 -0
- package/skills/hefesto-security/references/secrets-detection.md +121 -0
- package/skills/hefesto-security/references/severity-and-judgment.md +176 -0
- package/skills/hefesto-simplify/SKILL.md +82 -0
- package/templates/TPL-CLAUDE.md +54 -0
- package/templates/TPL-CONFIG.json +19 -0
- package/templates/TPL-DESIGN.md +305 -0
- package/templates/{FEATURE.md → TPL-FEATURE.md} +13 -6
- package/templates/TPL-PROJECT.md +50 -0
- package/templates/{RECON.md → TPL-RECON.md} +10 -4
- package/templates/{RESEARCH.md → TPL-RESEARCH.md} +15 -15
- package/templates/TPL-SECURITY.md +42 -0
- package/templates/TPL-SIMPLIFY.md +40 -0
- package/templates/{STATE.md → TPL-STATE.md} +0 -6
- package/templates/TPL-VERDICT.md +34 -0
- package/skills/hefesto-design/data/animations.csv +0 -21
- package/skills/hefesto-design/data/anti-patterns.csv +0 -41
- package/skills/hefesto-design/data/charts.csv +0 -26
- package/skills/hefesto-design/data/colors.csv +0 -108
- package/skills/hefesto-design/data/components.csv +0 -31
- package/skills/hefesto-design/data/google-fonts.csv +0 -56
- package/skills/hefesto-design/data/icons.csv +0 -23
- package/skills/hefesto-design/data/landing-pages.csv +0 -28
- package/skills/hefesto-design/data/products.csv +0 -46
- package/skills/hefesto-design/data/spacing.csv +0 -16
- package/skills/hefesto-design/data/styles.csv +0 -53
- package/skills/hefesto-design/data/typography.csv +0 -41
- package/skills/hefesto-design/data/ux-rules.csv +0 -61
- package/skills/hefesto-design/references/accessibility.md +0 -335
- package/skills/hefesto-design/references/aesthetics.md +0 -343
- package/skills/hefesto-design/references/anti-patterns.md +0 -107
- package/skills/hefesto-design/references/checklist.md +0 -66
- package/skills/hefesto-design/references/color-psychology.md +0 -203
- package/skills/hefesto-design/references/component-specs.md +0 -318
- package/skills/hefesto-design/references/polish.md +0 -339
- package/skills/hefesto-design/references/token-architecture.md +0 -394
- package/skills/hefesto-design/references/ux-rules.md +0 -349
- package/skills/hefesto-design/scripts/__pycache__/audit.cpython-314.pyc +0 -0
- package/skills/hefesto-design/scripts/__pycache__/contrast.cpython-314.pyc +0 -0
- package/skills/hefesto-design/scripts/__pycache__/core.cpython-314.pyc +0 -0
- package/skills/hefesto-design/scripts/__pycache__/design_system.cpython-314.pyc +0 -0
- package/skills/hefesto-design/scripts/__pycache__/search.cpython-314.pyc +0 -0
- package/skills/hefesto-design/scripts/__pycache__/validate_tokens.cpython-314.pyc +0 -0
- package/skills/hefesto-design/scripts/audit.py +0 -450
- package/skills/hefesto-design/scripts/contrast.py +0 -195
- package/skills/hefesto-design/scripts/core.py +0 -155
- package/skills/hefesto-design/scripts/design_system.py +0 -311
- package/skills/hefesto-design/scripts/search.py +0 -235
- package/skills/hefesto-design/scripts/validate_tokens.py +0 -274
- package/skills/hefesto-update/SKILL.md +0 -34
- package/templates/DESIGN.md +0 -137
- package/templates/PROJECT.md +0 -28
- package/templates/ROADMAP.md +0 -23
- package/templates/VERDICT.md +0 -52
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Motor de busca BM25 reutilizável para os scripts de design do Hefesto.
|
|
3
|
-
|
|
4
|
-
Carrega CSVs, constrói índice invertido e ranqueia resultados usando BM25.
|
|
5
|
-
Usado como módulo por search.py, design_system.py e outros scripts.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import csv
|
|
9
|
-
import math
|
|
10
|
-
import re
|
|
11
|
-
import unicodedata
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
|
|
14
|
-
STOP_WORDS = frozenset({
|
|
15
|
-
# Português BR
|
|
16
|
-
"de", "da", "do", "das", "dos", "em", "no", "na", "nos", "nas",
|
|
17
|
-
"um", "uma", "uns", "umas", "ao", "aos", "por", "para", "com",
|
|
18
|
-
"sem", "sob", "sobre", "entre", "que", "se", "ou", "mas", "nem",
|
|
19
|
-
"como", "mais", "muito", "bem", "mal", "ja", "ainda", "tambem",
|
|
20
|
-
"so", "apenas", "ate", "desde", "esse", "essa", "este", "esta",
|
|
21
|
-
"isso", "isto", "aquilo", "ele", "ela", "eles", "elas", "nos",
|
|
22
|
-
"vos", "lhe", "lhes", "me", "te", "meu", "minha", "seu", "sua",
|
|
23
|
-
"teu", "tua", "nosso", "nossa", "vosso", "vossa", "ser", "estar",
|
|
24
|
-
"ter", "haver", "ir", "vir", "fazer", "dar", "dizer", "ver",
|
|
25
|
-
"saber", "poder", "querer", "dever", "foi", "sao", "era", "tem",
|
|
26
|
-
# English
|
|
27
|
-
"the", "be", "to", "of", "and", "in", "that", "have", "it",
|
|
28
|
-
"for", "not", "on", "with", "he", "as", "you", "do", "at",
|
|
29
|
-
"this", "but", "his", "by", "from", "they", "we", "say", "her",
|
|
30
|
-
"she", "or", "an", "will", "my", "one", "all", "would", "there",
|
|
31
|
-
"their", "what", "so", "up", "out", "if", "about", "who", "get",
|
|
32
|
-
"which", "go", "me", "when", "make", "can", "like", "no", "just",
|
|
33
|
-
"him", "know", "take", "how", "come", "could", "them", "see",
|
|
34
|
-
"than", "now", "its", "is", "are", "was", "were", "been", "has",
|
|
35
|
-
"had", "did", "does", "a", "am",
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
def _remove_accents(text):
|
|
39
|
-
"""Remove acentos usando unicodedata NFKD."""
|
|
40
|
-
nfkd = unicodedata.normalize("NFKD", text)
|
|
41
|
-
return "".join(c for c in nfkd if not unicodedata.combining(c))
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class BM25:
|
|
45
|
-
"""Motor de busca BM25 sobre arquivos CSV."""
|
|
46
|
-
|
|
47
|
-
def __init__(self, csv_path, search_fields, k1=1.5, b=0.75):
|
|
48
|
-
"""Carrega CSV e constrói índice invertido.
|
|
49
|
-
|
|
50
|
-
Args:
|
|
51
|
-
csv_path: Caminho para o arquivo CSV.
|
|
52
|
-
search_fields: Lista de nomes de colunas para indexar.
|
|
53
|
-
k1: Parâmetro de saturação de termo (padrão 1.5).
|
|
54
|
-
b: Parâmetro de normalização por comprimento (padrão 0.75).
|
|
55
|
-
"""
|
|
56
|
-
self.k1 = k1
|
|
57
|
-
self.b = b
|
|
58
|
-
self.search_fields = search_fields
|
|
59
|
-
self.docs = []
|
|
60
|
-
self.doc_lengths = []
|
|
61
|
-
self.inverted_index = {} # token → set of doc IDs
|
|
62
|
-
self.doc_term_freq = [] # doc_id → {token: count}
|
|
63
|
-
self.avg_dl = 0.0
|
|
64
|
-
|
|
65
|
-
self._load_csv(csv_path)
|
|
66
|
-
self._build_index()
|
|
67
|
-
|
|
68
|
-
def _load_csv(self, csv_path):
|
|
69
|
-
"""Carrega documentos do CSV."""
|
|
70
|
-
path = Path(csv_path)
|
|
71
|
-
if not path.exists():
|
|
72
|
-
raise FileNotFoundError(f"Arquivo não encontrado: {csv_path}")
|
|
73
|
-
with open(path, "r", encoding="utf-8") as f:
|
|
74
|
-
reader = csv.DictReader(f)
|
|
75
|
-
for row in reader:
|
|
76
|
-
self.docs.append(dict(row))
|
|
77
|
-
|
|
78
|
-
def _build_index(self):
|
|
79
|
-
"""Constrói índice invertido e calcula estatísticas."""
|
|
80
|
-
total_length = 0
|
|
81
|
-
for doc_id, doc in enumerate(self.docs):
|
|
82
|
-
text = " ".join(doc.get(f, "") or "" for f in self.search_fields)
|
|
83
|
-
tokens = self.tokenize(text)
|
|
84
|
-
self.doc_lengths.append(len(tokens))
|
|
85
|
-
total_length += len(tokens)
|
|
86
|
-
|
|
87
|
-
tf = {}
|
|
88
|
-
for token in tokens:
|
|
89
|
-
tf[token] = tf.get(token, 0) + 1
|
|
90
|
-
if token not in self.inverted_index:
|
|
91
|
-
self.inverted_index[token] = set()
|
|
92
|
-
self.inverted_index[token].add(doc_id)
|
|
93
|
-
|
|
94
|
-
self.doc_term_freq.append(tf)
|
|
95
|
-
|
|
96
|
-
n = len(self.docs)
|
|
97
|
-
self.avg_dl = total_length / n if n > 0 else 1.0
|
|
98
|
-
|
|
99
|
-
def tokenize(self, text):
|
|
100
|
-
"""Tokeniza texto: lowercase, remove acentos, split, filtra stop words.
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
text: Texto para tokenizar.
|
|
104
|
-
|
|
105
|
-
Returns:
|
|
106
|
-
Lista de tokens filtrados.
|
|
107
|
-
"""
|
|
108
|
-
text = _remove_accents(text.lower())
|
|
109
|
-
tokens = re.split(r"[^a-z0-9]+", text)
|
|
110
|
-
return [t for t in tokens if len(t) >= 2 and t not in STOP_WORDS]
|
|
111
|
-
|
|
112
|
-
def search(self, query, max_results=5):
|
|
113
|
-
"""Busca e retorna resultados ranqueados por BM25.
|
|
114
|
-
|
|
115
|
-
Args:
|
|
116
|
-
query: Texto da consulta.
|
|
117
|
-
max_results: Número máximo de resultados.
|
|
118
|
-
|
|
119
|
-
Returns:
|
|
120
|
-
Lista de dicts com os documentos e score.
|
|
121
|
-
"""
|
|
122
|
-
query_tokens = self.tokenize(query)
|
|
123
|
-
if not query_tokens:
|
|
124
|
-
return []
|
|
125
|
-
|
|
126
|
-
n = len(self.docs)
|
|
127
|
-
scores = {}
|
|
128
|
-
|
|
129
|
-
for token in query_tokens:
|
|
130
|
-
if token not in self.inverted_index:
|
|
131
|
-
continue
|
|
132
|
-
doc_ids = self.inverted_index[token]
|
|
133
|
-
df = len(doc_ids)
|
|
134
|
-
idf = math.log((n - df + 0.5) / (df + 0.5) + 1.0)
|
|
135
|
-
|
|
136
|
-
for doc_id in doc_ids:
|
|
137
|
-
tf = self.doc_term_freq[doc_id].get(token, 0)
|
|
138
|
-
dl = self.doc_lengths[doc_id]
|
|
139
|
-
numerator = tf * (self.k1 + 1)
|
|
140
|
-
denominator = tf + self.k1 * (1 - self.b + self.b * dl / self.avg_dl)
|
|
141
|
-
score = idf * numerator / denominator
|
|
142
|
-
scores[doc_id] = scores.get(doc_id, 0.0) + score
|
|
143
|
-
|
|
144
|
-
ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True)
|
|
145
|
-
results = []
|
|
146
|
-
for doc_id, score in ranked[:max_results]:
|
|
147
|
-
result = dict(self.docs[doc_id])
|
|
148
|
-
result["_score"] = round(score, 4)
|
|
149
|
-
results.append(result)
|
|
150
|
-
|
|
151
|
-
return results
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if __name__ == "__main__":
|
|
155
|
-
print("core.py é um módulo. Importe BM25 de outro script.")
|
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Gerador de sistema de design coerente para o Hefesto.
|
|
3
|
-
|
|
4
|
-
Busca em múltiplos domínios e produz uma recomendação unificada de
|
|
5
|
-
estilo, cores, tipografia, espaçamento, animações, regras de UX e
|
|
6
|
-
anti-patterns a evitar.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import argparse
|
|
10
|
-
import json
|
|
11
|
-
import sys
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
|
|
14
|
-
_SCRIPT_DIR = Path(__file__).resolve().parent
|
|
15
|
-
sys.path.insert(0, str(_SCRIPT_DIR))
|
|
16
|
-
|
|
17
|
-
from core import BM25
|
|
18
|
-
|
|
19
|
-
DATA_DIR = _SCRIPT_DIR.parent / "data"
|
|
20
|
-
|
|
21
|
-
# Regras de raciocínio: estilo → preferências de sub-domínios
|
|
22
|
-
REASONING = {
|
|
23
|
-
"brutalist": {"typography_style": "monospace", "palette_type": "high-contrast", "spacing_style": "dense", "animation_style": "minimal"},
|
|
24
|
-
"glassmorphism": {"typography_style": "geometric", "palette_type": "gradient-translucent", "spacing_style": "generous", "animation_style": "fade blur"},
|
|
25
|
-
"minimalist": {"typography_style": "clean sans", "palette_type": "monochromatic", "spacing_style": "generous", "animation_style": "subtle fade"},
|
|
26
|
-
"maximalist": {"typography_style": "display mixed", "palette_type": "multi-chromatic", "spacing_style": "dense", "animation_style": "parallax scroll"},
|
|
27
|
-
"retro-futuristic": {"typography_style": "display bold", "palette_type": "neon-dark", "spacing_style": "moderate", "animation_style": "glow scanner"},
|
|
28
|
-
"organic-natural": {"typography_style": "rounded", "palette_type": "earth-tones", "spacing_style": "relaxed", "animation_style": "gentle flow"},
|
|
29
|
-
"editorial-magazine": {"typography_style": "serif", "palette_type": "neutral-accent", "spacing_style": "generous", "animation_style": "scroll reveal"},
|
|
30
|
-
"art-deco": {"typography_style": "serif condensed","palette_type": "gold-dark", "spacing_style": "symmetric", "animation_style": "elegant reveal"},
|
|
31
|
-
"soft-pastel": {"typography_style": "rounded sans", "palette_type": "pastel", "spacing_style": "generous", "animation_style": "bounce elastic"},
|
|
32
|
-
"industrial-utilitarian": {"typography_style": "condensed mono", "palette_type": "grey-safety", "spacing_style": "compact", "animation_style": "functional"},
|
|
33
|
-
"luxury-refined": {"typography_style": "serif", "palette_type": "dark-gold", "spacing_style": "generous", "animation_style": "subtle parallax"},
|
|
34
|
-
"playful-toy": {"typography_style": "rounded bold", "palette_type": "primary-saturated", "spacing_style": "generous", "animation_style": "bouncy playful"},
|
|
35
|
-
"neo-memphis": {"typography_style": "geometric bold", "palette_type": "vibrant-contrast", "spacing_style": "moderate", "animation_style": "rotation scale"},
|
|
36
|
-
"swiss-international": {"typography_style": "neutral sans", "palette_type": "bw-accent", "spacing_style": "rigid-grid", "animation_style": "minimal fast"},
|
|
37
|
-
"dark-mode-premium": {"typography_style": "sans", "palette_type": "dark-layered", "spacing_style": "moderate", "animation_style": "subtle glow"},
|
|
38
|
-
"gradient-mesh": {"typography_style": "clean sans", "palette_type": "multi-gradient", "spacing_style": "generous", "animation_style": "gradient morph"},
|
|
39
|
-
"monochromatic": {"typography_style": "clean sans", "palette_type": "single-hue", "spacing_style": "generous", "animation_style": "tonal fade"},
|
|
40
|
-
"high-contrast": {"typography_style": "bold sans", "palette_type": "bw-accent", "spacing_style": "moderate", "animation_style": "assertive cut"},
|
|
41
|
-
"handcrafted-artisanal": {"typography_style": "handwritten", "palette_type": "warm-natural", "spacing_style": "organic", "animation_style": "sketch reveal"},
|
|
42
|
-
"cyberpunk-neon": {"typography_style": "monospace", "palette_type": "neon-dark", "spacing_style": "compact", "animation_style": "glitch flicker"},
|
|
43
|
-
"scandinavian": {"typography_style": "humanist sans", "palette_type": "warm-neutral", "spacing_style": "airy", "animation_style": "subtle micro"},
|
|
44
|
-
"wabi-sabi": {"typography_style": "delicate serif", "palette_type": "earth-stone", "spacing_style": "generous", "animation_style": "slow breath"},
|
|
45
|
-
"bauhaus": {"typography_style": "geometric pure", "palette_type": "primary-bw", "spacing_style": "modular", "animation_style": "geometric scale"},
|
|
46
|
-
"material-you": {"typography_style": "variable sans", "palette_type": "dynamic-tonal", "spacing_style": "moderate", "animation_style": "container shared"},
|
|
47
|
-
"neumorphism": {"typography_style": "clean sans", "palette_type": "monochromatic-soft", "spacing_style": "generous", "animation_style": "shadow shift"},
|
|
48
|
-
"flat-design": {"typography_style": "geometric sans", "palette_type": "solid-vibrant", "spacing_style": "uniform", "animation_style": "color position"},
|
|
49
|
-
"skeuomorphic": {"typography_style": "serif textured", "palette_type": "realistic-textured", "spacing_style": "metaphor", "animation_style": "physics depth"},
|
|
50
|
-
"retro-vintage": {"typography_style": "retro serif", "palette_type": "sepia-warm", "spacing_style": "moderate", "animation_style": "grain fade"},
|
|
51
|
-
"vaporwave": {"typography_style": "chrome display", "palette_type": "pink-purple-cyan", "spacing_style": "moderate", "animation_style": "drift vhs"},
|
|
52
|
-
"corporate-modern": {"typography_style": "neutral sans", "palette_type": "blue-neutral", "spacing_style": "standard", "animation_style": "minimal hover"},
|
|
53
|
-
"ai-native": {"typography_style": "variable sans", "palette_type": "dark-gradient", "spacing_style": "moderate", "animation_style": "streaming reveal"},
|
|
54
|
-
"dashboard-dense": {"typography_style": "condensed sans", "palette_type": "neutral-functional", "spacing_style": "tight", "animation_style": "data transition"},
|
|
55
|
-
"bento-grid": {"typography_style": "bold sans", "palette_type": "neutral-accent", "spacing_style": "modular", "animation_style": "card hover"},
|
|
56
|
-
"claymorphism": {"typography_style": "rounded bold", "palette_type": "pastel-saturated", "spacing_style": "generous", "animation_style": "squish bounce"},
|
|
57
|
-
"y2k-revival": {"typography_style": "pixel display", "palette_type": "chrome-candy", "spacing_style": "moderate", "animation_style": "sparkle blink"},
|
|
58
|
-
"acid-graphics": {"typography_style": "distorted display", "palette_type": "acid-neon", "spacing_style": "chaotic", "animation_style": "warp distort"},
|
|
59
|
-
"kinetic-typography": {"typography_style": "variable display", "palette_type": "monochromatic", "spacing_style": "generous", "animation_style": "text motion"},
|
|
60
|
-
"data-visualization": {"typography_style": "tabular sans", "palette_type": "sequential-diverging","spacing_style": "tight", "animation_style": "data morph"},
|
|
61
|
-
"dark-academia": {"typography_style": "classic serif", "palette_type": "sepia-dark", "spacing_style": "generous", "animation_style": "page turn"},
|
|
62
|
-
"cottagecore-digital": {"typography_style": "rounded serif", "palette_type": "warm-pastel", "spacing_style": "generous", "animation_style": "gentle fade"},
|
|
63
|
-
"afrofuturism": {"typography_style": "geometric bold", "palette_type": "gold-vibrant", "spacing_style": "moderate", "animation_style": "tribal pulse"},
|
|
64
|
-
"solarpunk": {"typography_style": "rounded sans", "palette_type": "green-solar", "spacing_style": "generous", "animation_style": "organic grow"},
|
|
65
|
-
"retro-computing": {"typography_style": "monospace", "palette_type": "phosphor-dark", "spacing_style": "rigid-grid", "animation_style": "terminal type"},
|
|
66
|
-
"pixel-art": {"typography_style": "pixel bitmap", "palette_type": "limited-palette", "spacing_style": "grid-8px", "animation_style": "frame step"},
|
|
67
|
-
"isometric": {"typography_style": "geometric sans", "palette_type": "flat-vibrant", "spacing_style": "modular", "animation_style": "perspective shift"},
|
|
68
|
-
"3d-immersive": {"typography_style": "variable sans", "palette_type": "atmospheric-dark", "spacing_style": "generous", "animation_style": "parallax depth"},
|
|
69
|
-
"psychedelic": {"typography_style": "display organic","palette_type": "rainbow-saturated", "spacing_style": "fluid", "animation_style": "morph flow"},
|
|
70
|
-
"magazine-grid": {"typography_style": "serif mixed", "palette_type": "neutral-accent", "spacing_style": "asymmetric", "animation_style": "scroll reveal"},
|
|
71
|
-
"text-only": {"typography_style": "serif or sans", "palette_type": "monochromatic", "spacing_style": "generous", "animation_style": "minimal fade"},
|
|
72
|
-
"micro-interaction-heavy":{"typography_style": "clean sans", "palette_type": "neutral-accent", "spacing_style": "moderate", "animation_style": "rich micro"},
|
|
73
|
-
"neobrutalism": {"typography_style": "bold sans", "palette_type": "vibrant-contrast", "spacing_style": "moderate", "animation_style": "hard cut"},
|
|
74
|
-
"japanese-minimal": {"typography_style": "delicate sans", "palette_type": "warm-neutral", "spacing_style": "very-generous","animation_style": "zen fade"},
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
DOMAIN_CONFIGS = {
|
|
78
|
-
"style": ("styles.csv", ["name", "keywords_pt", "keywords_en", "description", "mood", "best_for"]),
|
|
79
|
-
"color": ("colors.csv", ["name", "industry", "mood", "description"]),
|
|
80
|
-
"typography": ("typography.csv", ["name", "mood", "best_for", "display_font", "body_font", "pairing_reason"]),
|
|
81
|
-
"spacing": ("spacing.csv", ["name", "style", "description", "density"]),
|
|
82
|
-
"animation": ("animations.csv", ["name", "context", "description"]),
|
|
83
|
-
"anti-pattern": ("anti-patterns.csv",["name", "category", "description", "why_bad"]),
|
|
84
|
-
"ux": ("ux-rules.csv", ["name", "category", "rule", "standard", "reasoning"]),
|
|
85
|
-
"product": ("products.csv", ["name", "category", "audience", "recommended_style", "key_components", "layout_pattern", "description"]),
|
|
86
|
-
"chart": ("charts.csv", ["name", "category", "best_for", "data_type", "a11y_notes", "description"]),
|
|
87
|
-
"landing": ("landing-pages.csv",["name", "section_type", "purpose", "key_elements", "copywriting_pattern", "description"]),
|
|
88
|
-
"icon": ("icons.csv", ["name", "style", "best_for", "avoid_with", "description"]),
|
|
89
|
-
"font": ("google-fonts.csv", ["name", "category", "mood", "best_for", "pair_with", "description"]),
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def _load_engine(domain):
|
|
94
|
-
"""Carrega BM25 para um domínio, retorna None se CSV não existir."""
|
|
95
|
-
csv_file, fields = DOMAIN_CONFIGS[domain]
|
|
96
|
-
csv_path = DATA_DIR / csv_file
|
|
97
|
-
if not csv_path.exists():
|
|
98
|
-
return None
|
|
99
|
-
return BM25(str(csv_path), fields)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def _safe_search(engine, query, n=3):
|
|
103
|
-
"""Busca segura, retorna lista vazia se engine for None."""
|
|
104
|
-
if engine is None:
|
|
105
|
-
return []
|
|
106
|
-
return engine.search(query, n)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def generate_system(query, project_name=None):
|
|
110
|
-
"""Gera recomendação de design system completa."""
|
|
111
|
-
engines = {k: _load_engine(k) for k in DOMAIN_CONFIGS}
|
|
112
|
-
|
|
113
|
-
# 1. Buscar estilos
|
|
114
|
-
styles = _safe_search(engines["style"], query, 3)
|
|
115
|
-
|
|
116
|
-
if not styles:
|
|
117
|
-
# Nenhum estilo encontrado — retorna resultado vazio sem crashar
|
|
118
|
-
return {
|
|
119
|
-
"project": project_name or "Projeto",
|
|
120
|
-
"query": query,
|
|
121
|
-
"style": [],
|
|
122
|
-
"reasoning": {},
|
|
123
|
-
"colors": _safe_search(engines["color"], query, 3),
|
|
124
|
-
"typography": _safe_search(engines["typography"], query, 3),
|
|
125
|
-
"spacing": _safe_search(engines["spacing"], query, 2),
|
|
126
|
-
"animations": _safe_search(engines["animation"], query, 3),
|
|
127
|
-
"products": _safe_search(engines.get("product"), query, 3) if "product" in engines else [],
|
|
128
|
-
"charts": _safe_search(engines.get("chart"), query, 3) if "chart" in engines else [],
|
|
129
|
-
"landing": _safe_search(engines.get("landing"), query, 3) if "landing" in engines else [],
|
|
130
|
-
"icons": _safe_search(engines.get("icon"), query, 3) if "icon" in engines else [],
|
|
131
|
-
"fonts": _safe_search(engines.get("font"), query, 3) if "font" in engines else [],
|
|
132
|
-
"anti_patterns": _safe_search(engines["anti-pattern"], query, 3),
|
|
133
|
-
"ux_rules": _safe_search(engines["ux"], query, 5),
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
top_style_name = styles[0].get("name", "").lower().replace(" ", "-")
|
|
137
|
-
|
|
138
|
-
# 2. Enriquecer query com raciocínio do estilo
|
|
139
|
-
reasoning = REASONING.get(top_style_name, {})
|
|
140
|
-
color_query = query
|
|
141
|
-
typo_query = reasoning.get("typography_style", query)
|
|
142
|
-
spacing_query = reasoning.get("spacing_style", query)
|
|
143
|
-
anim_query = reasoning.get("animation_style", query)
|
|
144
|
-
|
|
145
|
-
# 3. Buscar sub-domínios
|
|
146
|
-
colors = _safe_search(engines["color"], color_query, 3)
|
|
147
|
-
typography = _safe_search(engines["typography"], f"{query} {typo_query}", 3)
|
|
148
|
-
spacing = _safe_search(engines["spacing"], f"{query} {spacing_query}", 2)
|
|
149
|
-
animations = _safe_search(engines["animation"], f"{query} {anim_query}", 3)
|
|
150
|
-
products = _safe_search(engines.get("product"), query, 3) if "product" in engines else []
|
|
151
|
-
charts = _safe_search(engines.get("chart"), query, 3) if "chart" in engines else []
|
|
152
|
-
landing = _safe_search(engines.get("landing"), query, 3) if "landing" in engines else []
|
|
153
|
-
icons = _safe_search(engines.get("icon"), query, 3) if "icon" in engines else []
|
|
154
|
-
fonts = _safe_search(engines.get("font"), f"{query} {typo_query}", 3) if "font" in engines else []
|
|
155
|
-
anti_patterns = _safe_search(engines["anti-pattern"], query, 3)
|
|
156
|
-
ux_rules = _safe_search(engines["ux"], query, 5)
|
|
157
|
-
|
|
158
|
-
return {
|
|
159
|
-
"project": project_name or "Projeto",
|
|
160
|
-
"query": query,
|
|
161
|
-
"style": styles,
|
|
162
|
-
"reasoning": reasoning,
|
|
163
|
-
"colors": colors,
|
|
164
|
-
"typography": typography,
|
|
165
|
-
"spacing": spacing,
|
|
166
|
-
"animations": animations,
|
|
167
|
-
"products": products,
|
|
168
|
-
"charts": charts,
|
|
169
|
-
"landing": landing,
|
|
170
|
-
"icons": icons,
|
|
171
|
-
"fonts": fonts,
|
|
172
|
-
"anti_patterns": anti_patterns,
|
|
173
|
-
"ux_rules": ux_rules,
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def _fmt_items(items, fields, indent=" "):
|
|
178
|
-
"""Formata lista de itens como linhas de texto."""
|
|
179
|
-
lines = []
|
|
180
|
-
for i, item in enumerate(items, 1):
|
|
181
|
-
parts = []
|
|
182
|
-
for f in fields:
|
|
183
|
-
val = item.get(f, "")
|
|
184
|
-
if val:
|
|
185
|
-
parts.append(f"{f}: {val}")
|
|
186
|
-
lines.append(f"{indent}{i}. " + " | ".join(parts))
|
|
187
|
-
return "\n".join(lines) if lines else f"{indent}(nenhum resultado)"
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
# Seções para formatação data-driven: (chave no dict, título texto, título markdown, campos)
|
|
191
|
-
_SECTIONS = [
|
|
192
|
-
("colors", "PALETAS DE CORES", "Paletas de Cores", ["name", "mood", "description"]),
|
|
193
|
-
("typography", "TIPOGRAFIA", "Tipografia", ["name", "display_font", "body_font", "pairing_reason"]),
|
|
194
|
-
("products", "TIPOS DE PRODUTO", "Tipos de Produto", ["name", "category", "recommended_style", "layout_pattern"]),
|
|
195
|
-
("spacing", "ESPAÇAMENTO", "Espaçamento", ["name", "style", "density"]),
|
|
196
|
-
("animations", "ANIMAÇÕES", "Animações", ["name", "context", "description"]),
|
|
197
|
-
("charts", "GRÁFICOS", "Gráficos", ["name", "category", "best_for"]),
|
|
198
|
-
("landing", "LANDING PAGES", "Landing Pages", ["name", "section_type", "purpose"]),
|
|
199
|
-
("icons", "ÍCONES", "Ícones", ["name", "style", "best_for"]),
|
|
200
|
-
("fonts", "GOOGLE FONTS", "Google Fonts", ["name", "category", "mood", "pair_with"]),
|
|
201
|
-
("anti_patterns","ANTI-PATTERNS A EVITAR", "Anti-Patterns a Evitar", ["name", "why_bad"]),
|
|
202
|
-
("ux_rules", "REGRAS DE UX", "Regras de UX", ["name", "rule"]),
|
|
203
|
-
]
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def format_text(system):
|
|
207
|
-
"""Formata como texto legível."""
|
|
208
|
-
lines = [
|
|
209
|
-
f"\n{'='*60}",
|
|
210
|
-
f" SISTEMA DE DESIGN — {system['project']}",
|
|
211
|
-
f" Consulta: \"{system['query']}\"",
|
|
212
|
-
f"{'='*60}",
|
|
213
|
-
"",
|
|
214
|
-
" ESTILO VISUAL",
|
|
215
|
-
_fmt_items(system["style"], ["name", "mood", "best_for"]),
|
|
216
|
-
"",
|
|
217
|
-
]
|
|
218
|
-
if system["reasoning"]:
|
|
219
|
-
r = system["reasoning"]
|
|
220
|
-
lines.append(" RACIOCÍNIO DE ESTILO")
|
|
221
|
-
lines.append(f" Tipografia recomendada: {r.get('typography_style', '-')}")
|
|
222
|
-
lines.append(f" Espaçamento recomendado: {r.get('spacing_style', '-')}")
|
|
223
|
-
lines.append(f" Animação recomendada: {r.get('animation_style', '-')}")
|
|
224
|
-
lines.append("")
|
|
225
|
-
|
|
226
|
-
for key, title_text, _, fields in _SECTIONS:
|
|
227
|
-
items = system.get(key, [])
|
|
228
|
-
if items:
|
|
229
|
-
lines.append(f" {title_text}")
|
|
230
|
-
lines.append(_fmt_items(items, fields))
|
|
231
|
-
lines.append("")
|
|
232
|
-
|
|
233
|
-
lines.append(f"\n{'='*60}\n")
|
|
234
|
-
return "\n".join(lines)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
def format_markdown(system):
|
|
238
|
-
"""Formata como Markdown estruturado."""
|
|
239
|
-
lines = [
|
|
240
|
-
f"# Sistema de Design — {system['project']}",
|
|
241
|
-
f"",
|
|
242
|
-
f"> Consulta: `{system['query']}`",
|
|
243
|
-
"",
|
|
244
|
-
"## Estilo Visual",
|
|
245
|
-
"",
|
|
246
|
-
]
|
|
247
|
-
for s in system["style"]:
|
|
248
|
-
lines.append(f"- **{s.get('name', '')}** — {s.get('mood', '')} | {s.get('best_for', '')}")
|
|
249
|
-
lines.append("")
|
|
250
|
-
|
|
251
|
-
if system["reasoning"]:
|
|
252
|
-
r = system["reasoning"]
|
|
253
|
-
lines.append("## Raciocínio de Estilo")
|
|
254
|
-
lines.append("")
|
|
255
|
-
lines.append(f"| Aspecto | Recomendação |")
|
|
256
|
-
lines.append(f"|---------|-------------|")
|
|
257
|
-
lines.append(f"| Tipografia | {r.get('typography_style', '-')} |")
|
|
258
|
-
lines.append(f"| Espaçamento | {r.get('spacing_style', '-')} |")
|
|
259
|
-
lines.append(f"| Animação | {r.get('animation_style', '-')} |")
|
|
260
|
-
lines.append("")
|
|
261
|
-
|
|
262
|
-
for key, _, title_md, fields in _SECTIONS:
|
|
263
|
-
items = system.get(key, [])
|
|
264
|
-
lines.append(f"## {title_md}")
|
|
265
|
-
lines.append("")
|
|
266
|
-
if items:
|
|
267
|
-
header = " | ".join(fields)
|
|
268
|
-
sep = " | ".join("---" for _ in fields)
|
|
269
|
-
lines.append(f"| {header} |")
|
|
270
|
-
lines.append(f"| {sep} |")
|
|
271
|
-
for item in items:
|
|
272
|
-
vals = " | ".join(str(item.get(f, "")) for f in fields)
|
|
273
|
-
lines.append(f"| {vals} |")
|
|
274
|
-
else:
|
|
275
|
-
lines.append("_Nenhum resultado encontrado._")
|
|
276
|
-
lines.append("")
|
|
277
|
-
|
|
278
|
-
return "\n".join(lines)
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
def main():
|
|
282
|
-
parser = argparse.ArgumentParser(
|
|
283
|
-
description="Gera sistema de design coerente a partir de uma consulta.",
|
|
284
|
-
epilog="""Exemplos:
|
|
285
|
-
python3 design_system.py "fintech modern dark"
|
|
286
|
-
python3 design_system.py "wellness organic calm" -p MeuProjeto
|
|
287
|
-
python3 design_system.py "saas dashboard" -f markdown
|
|
288
|
-
""",
|
|
289
|
-
)
|
|
290
|
-
parser.add_argument("query", help="Termos descritivos do projeto/estilo desejado")
|
|
291
|
-
parser.add_argument("-p", "--project", default=None, help="Nome do projeto")
|
|
292
|
-
parser.add_argument(
|
|
293
|
-
"-f", "--format",
|
|
294
|
-
choices=["text", "markdown", "json"],
|
|
295
|
-
default="text",
|
|
296
|
-
help="Formato de saída (padrão: text)",
|
|
297
|
-
)
|
|
298
|
-
|
|
299
|
-
args = parser.parse_args()
|
|
300
|
-
system = generate_system(args.query, args.project)
|
|
301
|
-
|
|
302
|
-
if args.format == "json":
|
|
303
|
-
print(json.dumps(system, indent=2, ensure_ascii=False))
|
|
304
|
-
elif args.format == "markdown":
|
|
305
|
-
print(format_markdown(system))
|
|
306
|
-
else:
|
|
307
|
-
print(format_text(system))
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if __name__ == "__main__":
|
|
311
|
-
main()
|