@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.
Files changed (73) hide show
  1. package/agents/hefesto-argos.md +51 -237
  2. package/agents/hefesto-athena.md +59 -339
  3. package/agents/hefesto-hermes.md +39 -71
  4. package/bin/install.js +105 -69
  5. package/hooks/hefesto-check-update.cjs +32 -11
  6. package/hooks/hefesto-statusline.cjs +8 -17
  7. package/hooks/hefesto-workflow.cjs +68 -0
  8. package/package.json +12 -2
  9. package/skills/hefesto-context/SKILL.md +59 -26
  10. package/skills/hefesto-debug/SKILL.md +54 -0
  11. package/skills/hefesto-design/SKILL.md +133 -143
  12. package/skills/hefesto-execute/SKILL.md +133 -0
  13. package/skills/hefesto-init/SKILL.md +94 -59
  14. package/skills/hefesto-init/references/api.md +116 -0
  15. package/skills/hefesto-init/references/cli.md +91 -0
  16. package/skills/hefesto-init/references/mobile.md +69 -0
  17. package/skills/hefesto-init/references/web.md +246 -0
  18. package/skills/hefesto-new-feature/SKILL.md +75 -41
  19. package/skills/hefesto-security/SKILL.md +89 -0
  20. package/skills/hefesto-security/references/boundaries-and-bypasses.md +152 -0
  21. package/skills/hefesto-security/references/secrets-detection.md +121 -0
  22. package/skills/hefesto-security/references/severity-and-judgment.md +176 -0
  23. package/skills/hefesto-simplify/SKILL.md +82 -0
  24. package/templates/TPL-CLAUDE.md +54 -0
  25. package/templates/TPL-CONFIG.json +19 -0
  26. package/templates/TPL-DESIGN.md +305 -0
  27. package/templates/{FEATURE.md → TPL-FEATURE.md} +13 -6
  28. package/templates/TPL-PROJECT.md +50 -0
  29. package/templates/{RECON.md → TPL-RECON.md} +10 -4
  30. package/templates/{RESEARCH.md → TPL-RESEARCH.md} +15 -15
  31. package/templates/TPL-SECURITY.md +42 -0
  32. package/templates/TPL-SIMPLIFY.md +40 -0
  33. package/templates/{STATE.md → TPL-STATE.md} +0 -6
  34. package/templates/TPL-VERDICT.md +34 -0
  35. package/skills/hefesto-design/data/animations.csv +0 -21
  36. package/skills/hefesto-design/data/anti-patterns.csv +0 -41
  37. package/skills/hefesto-design/data/charts.csv +0 -26
  38. package/skills/hefesto-design/data/colors.csv +0 -108
  39. package/skills/hefesto-design/data/components.csv +0 -31
  40. package/skills/hefesto-design/data/google-fonts.csv +0 -56
  41. package/skills/hefesto-design/data/icons.csv +0 -23
  42. package/skills/hefesto-design/data/landing-pages.csv +0 -28
  43. package/skills/hefesto-design/data/products.csv +0 -46
  44. package/skills/hefesto-design/data/spacing.csv +0 -16
  45. package/skills/hefesto-design/data/styles.csv +0 -53
  46. package/skills/hefesto-design/data/typography.csv +0 -41
  47. package/skills/hefesto-design/data/ux-rules.csv +0 -61
  48. package/skills/hefesto-design/references/accessibility.md +0 -335
  49. package/skills/hefesto-design/references/aesthetics.md +0 -343
  50. package/skills/hefesto-design/references/anti-patterns.md +0 -107
  51. package/skills/hefesto-design/references/checklist.md +0 -66
  52. package/skills/hefesto-design/references/color-psychology.md +0 -203
  53. package/skills/hefesto-design/references/component-specs.md +0 -318
  54. package/skills/hefesto-design/references/polish.md +0 -339
  55. package/skills/hefesto-design/references/token-architecture.md +0 -394
  56. package/skills/hefesto-design/references/ux-rules.md +0 -349
  57. package/skills/hefesto-design/scripts/__pycache__/audit.cpython-314.pyc +0 -0
  58. package/skills/hefesto-design/scripts/__pycache__/contrast.cpython-314.pyc +0 -0
  59. package/skills/hefesto-design/scripts/__pycache__/core.cpython-314.pyc +0 -0
  60. package/skills/hefesto-design/scripts/__pycache__/design_system.cpython-314.pyc +0 -0
  61. package/skills/hefesto-design/scripts/__pycache__/search.cpython-314.pyc +0 -0
  62. package/skills/hefesto-design/scripts/__pycache__/validate_tokens.cpython-314.pyc +0 -0
  63. package/skills/hefesto-design/scripts/audit.py +0 -450
  64. package/skills/hefesto-design/scripts/contrast.py +0 -195
  65. package/skills/hefesto-design/scripts/core.py +0 -155
  66. package/skills/hefesto-design/scripts/design_system.py +0 -311
  67. package/skills/hefesto-design/scripts/search.py +0 -235
  68. package/skills/hefesto-design/scripts/validate_tokens.py +0 -274
  69. package/skills/hefesto-update/SKILL.md +0 -34
  70. package/templates/DESIGN.md +0 -137
  71. package/templates/PROJECT.md +0 -28
  72. package/templates/ROADMAP.md +0 -23
  73. 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()