@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,235 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""CLI de busca multi-domínio para o sistema de design do Hefesto.
|
|
3
|
-
|
|
4
|
-
Busca em CSVs de estilos, cores, tipografia, componentes, UX, anti-patterns,
|
|
5
|
-
espaçamento e animações usando BM25.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import argparse
|
|
9
|
-
import json
|
|
10
|
-
import sys
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
|
|
13
|
-
# Resolve import do core.py no mesmo diretório
|
|
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
|
-
DOMAINS = {
|
|
22
|
-
"style": {
|
|
23
|
-
"csv": "styles.csv",
|
|
24
|
-
"fields": ["name", "keywords_pt", "keywords_en", "description", "mood", "best_for"],
|
|
25
|
-
"label": "Estilos",
|
|
26
|
-
},
|
|
27
|
-
"color": {
|
|
28
|
-
"csv": "colors.csv",
|
|
29
|
-
"fields": ["name", "industry", "mood", "description"],
|
|
30
|
-
"label": "Paletas de Cores",
|
|
31
|
-
},
|
|
32
|
-
"typography": {
|
|
33
|
-
"csv": "typography.csv",
|
|
34
|
-
"fields": ["name", "mood", "best_for", "display_font", "body_font", "pairing_reason"],
|
|
35
|
-
"label": "Tipografia",
|
|
36
|
-
},
|
|
37
|
-
"component": {
|
|
38
|
-
"csv": "components.csv",
|
|
39
|
-
"fields": ["name", "category", "variants", "description"],
|
|
40
|
-
"label": "Componentes",
|
|
41
|
-
},
|
|
42
|
-
"ux": {
|
|
43
|
-
"csv": "ux-rules.csv",
|
|
44
|
-
"fields": ["name", "category", "rule", "standard", "reasoning"],
|
|
45
|
-
"label": "Regras de UX",
|
|
46
|
-
},
|
|
47
|
-
"anti-pattern": {
|
|
48
|
-
"csv": "anti-patterns.csv",
|
|
49
|
-
"fields": ["name", "category", "description", "why_bad"],
|
|
50
|
-
"label": "Anti-Patterns",
|
|
51
|
-
},
|
|
52
|
-
"spacing": {
|
|
53
|
-
"csv": "spacing.csv",
|
|
54
|
-
"fields": ["name", "style", "description", "density"],
|
|
55
|
-
"label": "Espaçamento",
|
|
56
|
-
},
|
|
57
|
-
"animation": {
|
|
58
|
-
"csv": "animations.csv",
|
|
59
|
-
"fields": ["name", "context", "description"],
|
|
60
|
-
"label": "Animações",
|
|
61
|
-
},
|
|
62
|
-
"product": {
|
|
63
|
-
"csv": "products.csv",
|
|
64
|
-
"fields": ["name", "category", "audience", "recommended_style", "key_components", "layout_pattern", "description"],
|
|
65
|
-
"label": "Tipos de Produto",
|
|
66
|
-
},
|
|
67
|
-
"chart": {
|
|
68
|
-
"csv": "charts.csv",
|
|
69
|
-
"fields": ["name", "category", "best_for", "data_type", "a11y_notes", "description"],
|
|
70
|
-
"label": "Gráficos e Visualização",
|
|
71
|
-
},
|
|
72
|
-
"landing": {
|
|
73
|
-
"csv": "landing-pages.csv",
|
|
74
|
-
"fields": ["name", "section_type", "purpose", "key_elements", "copywriting_pattern", "description"],
|
|
75
|
-
"label": "Landing Pages",
|
|
76
|
-
},
|
|
77
|
-
"icon": {
|
|
78
|
-
"csv": "icons.csv",
|
|
79
|
-
"fields": ["name", "style", "best_for", "avoid_with", "description"],
|
|
80
|
-
"label": "Icon Sets",
|
|
81
|
-
},
|
|
82
|
-
"font": {
|
|
83
|
-
"csv": "google-fonts.csv",
|
|
84
|
-
"fields": ["name", "category", "mood", "best_for", "pair_with", "description"],
|
|
85
|
-
"label": "Google Fonts",
|
|
86
|
-
},
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def _get_engine(domain_key):
|
|
91
|
-
"""Cria um BM25 engine para o domínio dado."""
|
|
92
|
-
domain = DOMAINS[domain_key]
|
|
93
|
-
csv_path = DATA_DIR / domain["csv"]
|
|
94
|
-
if not csv_path.exists():
|
|
95
|
-
print(f"Erro: arquivo não encontrado: {csv_path}", file=sys.stderr)
|
|
96
|
-
sys.exit(1)
|
|
97
|
-
return BM25(str(csv_path), domain["fields"])
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def _display_columns(results):
|
|
101
|
-
"""Determina colunas visíveis (exclui internas e muito longas)."""
|
|
102
|
-
if not results:
|
|
103
|
-
return []
|
|
104
|
-
skip = {"_score"}
|
|
105
|
-
return [k for k in results[0].keys() if k not in skip]
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def _format_table(results, domain_label, max_col_width=40):
|
|
109
|
-
"""Formata resultados como tabela alinhada."""
|
|
110
|
-
if not results:
|
|
111
|
-
return f" Nenhum resultado encontrado para '{domain_label}'."
|
|
112
|
-
|
|
113
|
-
cols = _display_columns(results)
|
|
114
|
-
cols_with_score = cols + ["score"]
|
|
115
|
-
|
|
116
|
-
# Calcula largura de cada coluna
|
|
117
|
-
widths = {}
|
|
118
|
-
for col in cols_with_score:
|
|
119
|
-
header_len = len(col)
|
|
120
|
-
max_val = 0
|
|
121
|
-
for r in results:
|
|
122
|
-
val = str(r.get("_score", "")) if col == "score" else str(r.get(col, ""))
|
|
123
|
-
if len(val) > max_col_width:
|
|
124
|
-
val = val[:max_col_width - 3] + "..."
|
|
125
|
-
max_val = max(max_val, len(val))
|
|
126
|
-
widths[col] = max(header_len, min(max_val, max_col_width))
|
|
127
|
-
|
|
128
|
-
# Header
|
|
129
|
-
header = " | ".join(col.ljust(widths[col]) for col in cols_with_score)
|
|
130
|
-
separator = "-+-".join("-" * widths[col] for col in cols_with_score)
|
|
131
|
-
|
|
132
|
-
lines = [f"\n === {domain_label} ===\n", f" {header}", f" {separator}"]
|
|
133
|
-
|
|
134
|
-
for r in results:
|
|
135
|
-
row_parts = []
|
|
136
|
-
for col in cols_with_score:
|
|
137
|
-
val = str(r.get("_score", "")) if col == "score" else str(r.get(col, ""))
|
|
138
|
-
if len(val) > max_col_width:
|
|
139
|
-
val = val[:max_col_width - 3] + "..."
|
|
140
|
-
row_parts.append(val.ljust(widths[col]))
|
|
141
|
-
lines.append(" " + " | ".join(row_parts))
|
|
142
|
-
|
|
143
|
-
return "\n".join(lines)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def _search_domain(domain_key, query, max_results):
|
|
147
|
-
"""Busca em um domínio e retorna resultados."""
|
|
148
|
-
engine = _get_engine(domain_key)
|
|
149
|
-
return engine.search(query, max_results)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def _list_domains():
|
|
153
|
-
"""Lista todos os domínios disponíveis."""
|
|
154
|
-
print("\nDomínios disponíveis:\n")
|
|
155
|
-
for key, info in DOMAINS.items():
|
|
156
|
-
csv_path = DATA_DIR / info["csv"]
|
|
157
|
-
exists = "OK" if csv_path.exists() else "FALTANDO"
|
|
158
|
-
print(f" {key:15s} {info['label']:25s} [{exists}] {info['csv']}")
|
|
159
|
-
print()
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def main():
|
|
163
|
-
parser = argparse.ArgumentParser(
|
|
164
|
-
description="Busca multi-domínio no sistema de design Hefesto.",
|
|
165
|
-
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
166
|
-
epilog="""Exemplos:
|
|
167
|
-
python3 search.py "fintech modern" --domain style
|
|
168
|
-
python3 search.py "luxury serif" --domain typography
|
|
169
|
-
python3 search.py "wellness calm" --all
|
|
170
|
-
python3 search.py --list-domains
|
|
171
|
-
""",
|
|
172
|
-
)
|
|
173
|
-
parser.add_argument("query", nargs="?", default=None, help="Termos de busca")
|
|
174
|
-
parser.add_argument(
|
|
175
|
-
"--domain", "-d",
|
|
176
|
-
choices=list(DOMAINS.keys()),
|
|
177
|
-
help="Domínio para buscar",
|
|
178
|
-
)
|
|
179
|
-
parser.add_argument("--all", "-a", action="store_true", help="Buscar em todos os domínios")
|
|
180
|
-
parser.add_argument("--list-domains", action="store_true", help="Listar domínios disponíveis")
|
|
181
|
-
parser.add_argument(
|
|
182
|
-
"-f", "--format",
|
|
183
|
-
choices=["table", "json"],
|
|
184
|
-
default="table",
|
|
185
|
-
help="Formato de saída (padrão: table)",
|
|
186
|
-
)
|
|
187
|
-
parser.add_argument(
|
|
188
|
-
"-n", "--max-results",
|
|
189
|
-
type=int,
|
|
190
|
-
default=5,
|
|
191
|
-
help="Número máximo de resultados por domínio (padrão: 5)",
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
args = parser.parse_args()
|
|
195
|
-
|
|
196
|
-
if args.list_domains:
|
|
197
|
-
_list_domains()
|
|
198
|
-
return
|
|
199
|
-
|
|
200
|
-
if not args.query:
|
|
201
|
-
parser.error("Informe os termos de busca ou use --list-domains.")
|
|
202
|
-
|
|
203
|
-
if not args.domain and not args.all:
|
|
204
|
-
parser.error("Informe --domain DOMÍNIO ou --all para buscar em todos.")
|
|
205
|
-
|
|
206
|
-
domains_to_search = list(DOMAINS.keys()) if args.all else [args.domain]
|
|
207
|
-
|
|
208
|
-
all_results = {}
|
|
209
|
-
for domain_key in domains_to_search:
|
|
210
|
-
try:
|
|
211
|
-
results = _search_domain(domain_key, args.query, args.max_results)
|
|
212
|
-
if results:
|
|
213
|
-
all_results[domain_key] = results
|
|
214
|
-
except FileNotFoundError:
|
|
215
|
-
if not args.all:
|
|
216
|
-
print(f"Erro: CSV do domínio '{domain_key}' não encontrado.", file=sys.stderr)
|
|
217
|
-
sys.exit(1)
|
|
218
|
-
|
|
219
|
-
if args.format == "json":
|
|
220
|
-
output = {}
|
|
221
|
-
for domain_key, results in all_results.items():
|
|
222
|
-
output[domain_key] = results
|
|
223
|
-
print(json.dumps(output, indent=2, ensure_ascii=False))
|
|
224
|
-
else:
|
|
225
|
-
if not all_results:
|
|
226
|
-
print("\n Nenhum resultado encontrado.\n")
|
|
227
|
-
return
|
|
228
|
-
for domain_key, results in all_results.items():
|
|
229
|
-
label = DOMAINS[domain_key]["label"]
|
|
230
|
-
print(_format_table(results, label))
|
|
231
|
-
print()
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if __name__ == "__main__":
|
|
235
|
-
main()
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Validador de uso de design tokens para o Hefesto.
|
|
3
|
-
|
|
4
|
-
Verifica se tokens definidos no DESIGN.md são usados no código-fonte e
|
|
5
|
-
identifica valores hardcoded que deveriam usar tokens.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import argparse
|
|
9
|
-
import json
|
|
10
|
-
import os
|
|
11
|
-
import re
|
|
12
|
-
import sys
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
SCANNABLE_EXTENSIONS = {
|
|
16
|
-
".tsx", ".jsx", ".vue", ".svelte", ".css", ".scss",
|
|
17
|
-
".html", ".astro", ".ts", ".js",
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _is_binary(filepath):
|
|
22
|
-
"""Verifica se o arquivo parece ser binário."""
|
|
23
|
-
try:
|
|
24
|
-
with open(filepath, "rb") as f:
|
|
25
|
-
chunk = f.read(1024)
|
|
26
|
-
return b"\x00" in chunk
|
|
27
|
-
except (IOError, OSError):
|
|
28
|
-
return True
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def parse_tokens(design_path):
|
|
32
|
-
"""Extrai tokens de design da seção 'Tokens de Design' do DESIGN.md.
|
|
33
|
-
|
|
34
|
-
Retorna dict: {token_name: token_value}
|
|
35
|
-
Ex: {"--color-primary": "#6366f1", "--space-md": "16px"}
|
|
36
|
-
"""
|
|
37
|
-
try:
|
|
38
|
-
with open(design_path, "r", encoding="utf-8") as f:
|
|
39
|
-
content = f.read()
|
|
40
|
-
except FileNotFoundError:
|
|
41
|
-
print(f"Erro: arquivo não encontrado: {design_path}", file=sys.stderr)
|
|
42
|
-
sys.exit(1)
|
|
43
|
-
|
|
44
|
-
tokens = {}
|
|
45
|
-
in_section = False
|
|
46
|
-
|
|
47
|
-
for line in content.split("\n"):
|
|
48
|
-
# Detecta início da seção de tokens
|
|
49
|
-
if re.match(r"^#{1,4}\s+.*[Tt]okens\s+de\s+[Dd]esign", line):
|
|
50
|
-
in_section = True
|
|
51
|
-
continue
|
|
52
|
-
# Detecta fim da seção (próximo heading de mesmo nível ou superior)
|
|
53
|
-
if in_section and re.match(r"^#{1,3}\s+", line) and "token" not in line.lower():
|
|
54
|
-
break
|
|
55
|
-
if not in_section:
|
|
56
|
-
continue
|
|
57
|
-
|
|
58
|
-
# Extrai custom properties CSS: --nome: valor
|
|
59
|
-
token_match = re.search(r"(--[\w-]+)\s*:\s*([^;}\n]+)", line)
|
|
60
|
-
if token_match:
|
|
61
|
-
name = token_match.group(1).strip()
|
|
62
|
-
value = token_match.group(2).strip().rstrip(";")
|
|
63
|
-
tokens[name] = value
|
|
64
|
-
continue
|
|
65
|
-
|
|
66
|
-
# Também captura tokens em formato de tabela markdown: | --nome | valor |
|
|
67
|
-
table_match = re.search(r"\|\s*(--[\w-]+)\s*\|\s*([^|]+)\s*\|", line)
|
|
68
|
-
if table_match:
|
|
69
|
-
name = table_match.group(1).strip()
|
|
70
|
-
value = table_match.group(2).strip()
|
|
71
|
-
tokens[name] = value
|
|
72
|
-
continue
|
|
73
|
-
|
|
74
|
-
# Formato com backtick: `--nome` → `valor` ou `--nome`: `valor`
|
|
75
|
-
backtick_match = re.search(r"`(--[\w-]+)`\s*[:\u2192→=]\s*`?([^`\n]+)`?", line)
|
|
76
|
-
if backtick_match:
|
|
77
|
-
name = backtick_match.group(1).strip()
|
|
78
|
-
value = backtick_match.group(2).strip().rstrip("`")
|
|
79
|
-
tokens[name] = value
|
|
80
|
-
|
|
81
|
-
return tokens
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def walk_source(src_dir):
|
|
85
|
-
"""Percorre diretório fonte e retorna arquivos escaneáveis."""
|
|
86
|
-
files = []
|
|
87
|
-
for root, _dirs, filenames in os.walk(src_dir):
|
|
88
|
-
parts = root.split(os.sep)
|
|
89
|
-
if any(p in {"node_modules", ".git", "dist", "build", ".next", "__pycache__"} for p in parts):
|
|
90
|
-
continue
|
|
91
|
-
for fname in filenames:
|
|
92
|
-
ext = os.path.splitext(fname)[1].lower()
|
|
93
|
-
if ext in SCANNABLE_EXTENSIONS:
|
|
94
|
-
files.append(os.path.join(root, fname))
|
|
95
|
-
return files
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def validate_tokens(design_path, src_dir):
|
|
99
|
-
"""Valida uso de tokens no código-fonte.
|
|
100
|
-
|
|
101
|
-
Retorna dict com:
|
|
102
|
-
tokens_defined: total de tokens definidos
|
|
103
|
-
tokens_used: set de tokens encontrados no código
|
|
104
|
-
tokens_unused: set de tokens não encontrados
|
|
105
|
-
hardcoded_matches: lista de {file, line, token_name, hardcoded_value}
|
|
106
|
-
coverage: percentual de cobertura
|
|
107
|
-
"""
|
|
108
|
-
tokens = parse_tokens(design_path)
|
|
109
|
-
if not tokens:
|
|
110
|
-
print("Aviso: nenhum token encontrado na seção 'Tokens de Design'.", file=sys.stderr)
|
|
111
|
-
return {
|
|
112
|
-
"tokens_defined": 0,
|
|
113
|
-
"tokens_used": set(),
|
|
114
|
-
"tokens_unused": set(),
|
|
115
|
-
"hardcoded_matches": [],
|
|
116
|
-
"coverage": 0.0,
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
files = walk_source(src_dir)
|
|
120
|
-
tokens_used = set()
|
|
121
|
-
hardcoded_matches = []
|
|
122
|
-
|
|
123
|
-
# Pré-computa mapeamento valor → nomes de token (para detectar hardcoded)
|
|
124
|
-
value_to_tokens = {}
|
|
125
|
-
for name, value in tokens.items():
|
|
126
|
-
val_normalized = value.strip().lower()
|
|
127
|
-
if val_normalized and len(val_normalized) >= 2:
|
|
128
|
-
value_to_tokens.setdefault(val_normalized, []).append(name)
|
|
129
|
-
|
|
130
|
-
for filepath in files:
|
|
131
|
-
if _is_binary(filepath):
|
|
132
|
-
continue
|
|
133
|
-
try:
|
|
134
|
-
with open(filepath, "r", encoding="utf-8", errors="replace") as f:
|
|
135
|
-
lines = f.readlines()
|
|
136
|
-
except (IOError, OSError):
|
|
137
|
-
continue
|
|
138
|
-
|
|
139
|
-
file_content = "".join(lines)
|
|
140
|
-
|
|
141
|
-
# Verifica quais tokens são usados
|
|
142
|
-
for token_name in tokens:
|
|
143
|
-
if token_name in file_content:
|
|
144
|
-
tokens_used.add(token_name)
|
|
145
|
-
|
|
146
|
-
# Busca valores hardcoded que correspondem a valores de tokens
|
|
147
|
-
for lineno, line in enumerate(lines, 1):
|
|
148
|
-
# Ignora linhas de comentário
|
|
149
|
-
stripped = line.strip()
|
|
150
|
-
if (stripped.startswith("//") or stripped.startswith("/*")
|
|
151
|
-
or stripped.startswith("*") or stripped.startswith("#")
|
|
152
|
-
or stripped.startswith("<!--")):
|
|
153
|
-
continue
|
|
154
|
-
|
|
155
|
-
line_lower = line.lower()
|
|
156
|
-
|
|
157
|
-
for val_normalized, token_names in value_to_tokens.items():
|
|
158
|
-
# Use word boundary matching to avoid substring false positives
|
|
159
|
-
# e.g., token value "16" should not match "216px"
|
|
160
|
-
# Build a pattern that matches the value with surrounding context
|
|
161
|
-
escaped_val = re.escape(val_normalized)
|
|
162
|
-
# Match value with word boundaries or common delimiters
|
|
163
|
-
boundary_pattern = re.compile(
|
|
164
|
-
r"(?:^|[\s:=,;({\[])%s(?:$|[\s;,)}\]px%%rem em])" % escaped_val,
|
|
165
|
-
re.IGNORECASE,
|
|
166
|
-
)
|
|
167
|
-
if boundary_pattern.search(line_lower):
|
|
168
|
-
# Verifica se não está dentro de var() ou é definição do próprio token
|
|
169
|
-
is_var_usage = re.search(r"var\(\s*" + re.escape(token_names[0]), line)
|
|
170
|
-
is_definition = any(tn in line for tn in token_names)
|
|
171
|
-
if not is_var_usage and not is_definition:
|
|
172
|
-
hardcoded_matches.append({
|
|
173
|
-
"file": filepath,
|
|
174
|
-
"line": lineno,
|
|
175
|
-
"token_name": ", ".join(token_names),
|
|
176
|
-
"hardcoded_value": val_normalized,
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
tokens_unused = set(tokens.keys()) - tokens_used
|
|
180
|
-
total = len(tokens)
|
|
181
|
-
coverage = (len(tokens_used) / total * 100) if total > 0 else 0.0
|
|
182
|
-
|
|
183
|
-
return {
|
|
184
|
-
"tokens_defined": total,
|
|
185
|
-
"tokens_used": tokens_used,
|
|
186
|
-
"tokens_unused": tokens_unused,
|
|
187
|
-
"hardcoded_matches": hardcoded_matches,
|
|
188
|
-
"coverage": coverage,
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def format_report(result, fmt="table"):
|
|
193
|
-
"""Formata e imprime relatório de validação."""
|
|
194
|
-
if fmt == "json":
|
|
195
|
-
output = {
|
|
196
|
-
"tokens_defined": result["tokens_defined"],
|
|
197
|
-
"tokens_used": sorted(result["tokens_used"]),
|
|
198
|
-
"tokens_unused": sorted(result["tokens_unused"]),
|
|
199
|
-
"hardcoded_matches": result["hardcoded_matches"],
|
|
200
|
-
"coverage": round(result["coverage"], 1),
|
|
201
|
-
}
|
|
202
|
-
print(json.dumps(output, indent=2, ensure_ascii=False))
|
|
203
|
-
return
|
|
204
|
-
|
|
205
|
-
print(f"\n{'='*50}")
|
|
206
|
-
print(f" RELATÓRIO DE TOKENS DE DESIGN")
|
|
207
|
-
print(f"{'='*50}\n")
|
|
208
|
-
|
|
209
|
-
total = result["tokens_defined"]
|
|
210
|
-
used = len(result["tokens_used"])
|
|
211
|
-
unused = len(result["tokens_unused"])
|
|
212
|
-
|
|
213
|
-
print(f" Tokens definidos: {total}")
|
|
214
|
-
print(f" Tokens utilizados: {used}")
|
|
215
|
-
print(f" Tokens não usados: {unused}")
|
|
216
|
-
print(f" Cobertura: {result['coverage']:.1f}%\n")
|
|
217
|
-
|
|
218
|
-
if result["tokens_unused"]:
|
|
219
|
-
print(" TOKENS NÃO UTILIZADOS:")
|
|
220
|
-
for token in sorted(result["tokens_unused"]):
|
|
221
|
-
print(f" - {token}")
|
|
222
|
-
print()
|
|
223
|
-
|
|
224
|
-
if result["hardcoded_matches"]:
|
|
225
|
-
print(f" VALORES HARDCODED ({len(result['hardcoded_matches'])} ocorrências):")
|
|
226
|
-
for match in result["hardcoded_matches"]:
|
|
227
|
-
print(f" {match['file']}:{match['line']}")
|
|
228
|
-
print(f" Valor: {match['hardcoded_value']}")
|
|
229
|
-
print(f" Use: var({match['token_name']})")
|
|
230
|
-
print()
|
|
231
|
-
|
|
232
|
-
if not result["tokens_unused"] and not result["hardcoded_matches"]:
|
|
233
|
-
print(" Todos os tokens estão sendo utilizados corretamente!\n")
|
|
234
|
-
|
|
235
|
-
print(f"{'='*50}\n")
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
def main():
|
|
239
|
-
parser = argparse.ArgumentParser(
|
|
240
|
-
description="Validador de uso de design tokens.",
|
|
241
|
-
epilog="""Exemplos:
|
|
242
|
-
python3 validate_tokens.py --design .hefesto/DESIGN.md --src src/
|
|
243
|
-
python3 validate_tokens.py --design .hefesto/DESIGN.md --src src/ -f json
|
|
244
|
-
""",
|
|
245
|
-
)
|
|
246
|
-
parser.add_argument("--design", required=True, help="Caminho para DESIGN.md")
|
|
247
|
-
parser.add_argument("--src", required=True, help="Diretório fonte para escanear")
|
|
248
|
-
parser.add_argument(
|
|
249
|
-
"-f", "--format",
|
|
250
|
-
choices=["table", "json"],
|
|
251
|
-
default="table",
|
|
252
|
-
help="Formato de saída (padrão: table)",
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
args = parser.parse_args()
|
|
256
|
-
|
|
257
|
-
if not os.path.isfile(args.design):
|
|
258
|
-
print(f"Erro: DESIGN.md não encontrado: {args.design}", file=sys.stderr)
|
|
259
|
-
sys.exit(1)
|
|
260
|
-
|
|
261
|
-
if not os.path.isdir(args.src):
|
|
262
|
-
print(f"Erro: diretório fonte não encontrado: {args.src}", file=sys.stderr)
|
|
263
|
-
sys.exit(1)
|
|
264
|
-
|
|
265
|
-
result = validate_tokens(args.design, args.src)
|
|
266
|
-
format_report(result, args.format)
|
|
267
|
-
|
|
268
|
-
# Exit code não-zero se há tokens não usados ou valores hardcoded
|
|
269
|
-
if result["tokens_unused"] or result["hardcoded_matches"]:
|
|
270
|
-
sys.exit(1)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if __name__ == "__main__":
|
|
274
|
-
main()
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: hefesto-update
|
|
3
|
-
description: "Atualiza o Hefesto para a versão mais recente. Use /hefesto-update para atualizar skills, hooks e templates sem perder o estado do projeto."
|
|
4
|
-
user-invocable: true
|
|
5
|
-
disable-model-invocation: true
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Hefesto Update
|
|
9
|
-
|
|
10
|
-
Atualiza o toolkit Hefesto para a versão mais recente do npm.
|
|
11
|
-
|
|
12
|
-
## Pré-requisitos
|
|
13
|
-
|
|
14
|
-
Verificar se `.hefesto/` existe. Se não existir, informar que o projeto não foi inicializado e sugerir `/hefesto-init`.
|
|
15
|
-
|
|
16
|
-
## O que fazer
|
|
17
|
-
|
|
18
|
-
1. Ler `.hefesto/config.json` e anotar a versão atual (`version`).
|
|
19
|
-
2. Executar o comando de atualização:
|
|
20
|
-
```bash
|
|
21
|
-
npx @lugom.io/hefesto@latest
|
|
22
|
-
```
|
|
23
|
-
3. Verificar o output do comando. Se houver erro, informar o usuário e sugerir ação corretiva.
|
|
24
|
-
4. Ler `.hefesto/config.json` novamente e comparar a versão com a anterior.
|
|
25
|
-
5. Informar o resultado ao usuário:
|
|
26
|
-
- Se atualizou: mostrar versão anterior → nova versão
|
|
27
|
-
- Se já estava na última versão: informar que está atualizado
|
|
28
|
-
|
|
29
|
-
## Notas
|
|
30
|
-
|
|
31
|
-
- O installer preserva `.hefesto/` existente (PROJECT.md, STATE.md, ROADMAP.md, features/, config.json).
|
|
32
|
-
- O que é atualizado: skills, hooks, templates.
|
|
33
|
-
- Não usar `--global` a menos que o usuário peça explicitamente.
|
|
34
|
-
- Se o usuário pedir para atualizar um runtime específico (ex: `--gemini`), passar a flag correspondente.
|
package/templates/DESIGN.md
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
status: draft
|
|
3
|
-
depth: {{DESIGN_DEPTH}}
|
|
4
|
-
created: {{DATE}}
|
|
5
|
-
updated: {{DATE}}
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Design — {{PROJECT_NAME}}
|
|
9
|
-
|
|
10
|
-
## Direção Estética
|
|
11
|
-
|
|
12
|
-
**Estilo:** {{STYLE_NAME}}
|
|
13
|
-
**Propósito:** {{INTERFACE_PURPOSE}}
|
|
14
|
-
**Tom:** {{VISUAL_TONE}}
|
|
15
|
-
**Diferencial:** {{MEMORABLE_ELEMENT}}
|
|
16
|
-
|
|
17
|
-
> {{STYLE_DESCRIPTION}}
|
|
18
|
-
|
|
19
|
-
## Contrato de Cores
|
|
20
|
-
|
|
21
|
-
| Papel | Cor | Uso | % |
|
|
22
|
-
|-------|-----|-----|---|
|
|
23
|
-
| Dominante | {{DOMINANT_COLOR}} | {{DOMINANT_USE}} | 60% |
|
|
24
|
-
| Secundária | {{SECONDARY_COLOR}} | {{SECONDARY_USE}} | 30% |
|
|
25
|
-
| Acento | {{ACCENT_COLOR}} | **Reservado para:** {{ACCENT_RESERVED_LIST}} | 10% |
|
|
26
|
-
|
|
27
|
-
**Texto sobre dominante:** {{ON_DOMINANT}}
|
|
28
|
-
**Texto sobre secundária:** {{ON_SECONDARY}}
|
|
29
|
-
**Texto sobre acento:** {{ON_ACCENT}}
|
|
30
|
-
|
|
31
|
-
### Dark Mode
|
|
32
|
-
|
|
33
|
-
| Papel | Light | Dark |
|
|
34
|
-
|-------|-------|------|
|
|
35
|
-
| Dominante | {{DOMINANT_LIGHT}} | {{DOMINANT_DARK}} |
|
|
36
|
-
| Secundária | {{SECONDARY_LIGHT}} | {{SECONDARY_DARK}} |
|
|
37
|
-
| Acento | {{ACCENT_LIGHT}} | {{ACCENT_DARK}} |
|
|
38
|
-
|
|
39
|
-
**Contraste AA verificado:** {{CONTRAST_VERIFIED}}
|
|
40
|
-
|
|
41
|
-
## Contrato de Tipografia
|
|
42
|
-
|
|
43
|
-
| Nível | Font | Size | Weight | Line Height | Uso |
|
|
44
|
-
|-------|------|------|--------|-------------|-----|
|
|
45
|
-
| Display | {{DISPLAY_FONT}} | {{DISPLAY_SIZE}} | {{DISPLAY_WEIGHT}} | {{DISPLAY_LH}} | {{DISPLAY_USE}} |
|
|
46
|
-
| Heading | {{HEADING_FONT}} | {{HEADING_SIZE}} | {{HEADING_WEIGHT}} | {{HEADING_LH}} | {{HEADING_USE}} |
|
|
47
|
-
| Body | {{BODY_FONT}} | {{BODY_SIZE}} | {{BODY_WEIGHT}} | {{BODY_LH}} | {{BODY_USE}} |
|
|
48
|
-
| Small | {{SMALL_FONT}} | {{SMALL_SIZE}} | {{SMALL_WEIGHT}} | {{SMALL_LH}} | {{SMALL_USE}} |
|
|
49
|
-
|
|
50
|
-
> Max 4 tamanhos. Max 2 pesos. Sem exceções.
|
|
51
|
-
|
|
52
|
-
**Fontes bloqueadas:** Inter, Roboto, Arial, system-ui (a menos que justificado no estilo)
|
|
53
|
-
|
|
54
|
-
## Contrato de Espaçamento
|
|
55
|
-
|
|
56
|
-
**Escala:** `{{SPACING_SCALE}}`
|
|
57
|
-
**Unidade base:** {{BASE_UNIT}}px
|
|
58
|
-
**Touch targets:** mínimo {{TOUCH_MIN}}px
|
|
59
|
-
|
|
60
|
-
> Valor fora da escala = bug. Sem exceções.
|
|
61
|
-
|
|
62
|
-
## Copywriting
|
|
63
|
-
|
|
64
|
-
| Elemento | Texto |
|
|
65
|
-
|----------|-------|
|
|
66
|
-
| CTA primário | {{PRIMARY_CTA}} |
|
|
67
|
-
| CTA secundário | {{SECONDARY_CTA}} |
|
|
68
|
-
| Empty state | {{EMPTY_STATE}} |
|
|
69
|
-
| Error state | {{ERROR_STATE}} |
|
|
70
|
-
| Loading state | {{LOADING_STATE}} |
|
|
71
|
-
| Confirmação destrutiva | {{DESTRUCTIVE_CONFIRM}} |
|
|
72
|
-
|
|
73
|
-
**Tom de voz:** {{VOICE_TONE}}
|
|
74
|
-
|
|
75
|
-
<!-- standard+ -->
|
|
76
|
-
|
|
77
|
-
## Referências Visuais
|
|
78
|
-
|
|
79
|
-
| Referência | URL | O que extrair |
|
|
80
|
-
|------------|-----|---------------|
|
|
81
|
-
| {{REF_NAME}} | {{REF_URL}} | {{REF_EXTRACT}} |
|
|
82
|
-
|
|
83
|
-
## Tokens de Design
|
|
84
|
-
|
|
85
|
-
### Primitivos (Core)
|
|
86
|
-
|
|
87
|
-
```css
|
|
88
|
-
:root {
|
|
89
|
-
{{PRIMITIVE_TOKENS}}
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### Semânticos
|
|
94
|
-
|
|
95
|
-
```css
|
|
96
|
-
:root {
|
|
97
|
-
{{SEMANTIC_TOKENS}}
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### Componente
|
|
102
|
-
|
|
103
|
-
```css
|
|
104
|
-
{{COMPONENT_TOKENS}}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
## Overrides por Contexto
|
|
108
|
-
|
|
109
|
-
> Overrides são exceções justificadas ao contrato principal. Max 3 contextos.
|
|
110
|
-
> Exemplos: admin vs público, onboarding vs app, marketing vs produto.
|
|
111
|
-
|
|
112
|
-
| Contexto | O que muda | Valores | Justificativa |
|
|
113
|
-
|----------|-----------|---------|---------------|
|
|
114
|
-
| {{CONTEXT_NAME}} | {{WHAT_CHANGES}} | {{OVERRIDE_VALUES}} | {{JUSTIFICATION}} |
|
|
115
|
-
|
|
116
|
-
<!-- deep -->
|
|
117
|
-
|
|
118
|
-
## Specs de Componentes
|
|
119
|
-
|
|
120
|
-
| Componente | Nível | Variantes | Estados | A11y | Animação |
|
|
121
|
-
|------------|-------|-----------|---------|------|----------|
|
|
122
|
-
| {{COMP_NAME}} | {{ATOMIC_LEVEL}} | {{VARIANTS}} | {{STATES}} | {{A11Y}} | {{ANIMATION}} |
|
|
123
|
-
|
|
124
|
-
## Prompts para Ferramentas AI
|
|
125
|
-
|
|
126
|
-
### v0.dev / Lovable / Bolt
|
|
127
|
-
|
|
128
|
-
```
|
|
129
|
-
{{AI_PROMPT}}
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
<!-- /deep -->
|
|
133
|
-
<!-- /standard+ -->
|
|
134
|
-
|
|
135
|
-
## Anti-Padrões Bloqueados
|
|
136
|
-
|
|
137
|
-
- [ ] {{ANTI_PATTERN_CHECK}}
|