@lugom.io/hefesto 0.1.2 → 0.3.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 (51) hide show
  1. package/agents/hefesto-argos.md +279 -0
  2. package/agents/hefesto-athena.md +379 -0
  3. package/agents/hefesto-hermes.md +128 -0
  4. package/bin/install.js +97 -20
  5. package/package.json +3 -3
  6. package/skills/hefesto-context/SKILL.md +38 -6
  7. package/skills/hefesto-design/SKILL.md +194 -0
  8. package/skills/hefesto-design/data/animations.csv +21 -0
  9. package/skills/hefesto-design/data/anti-patterns.csv +41 -0
  10. package/skills/hefesto-design/data/charts.csv +26 -0
  11. package/skills/hefesto-design/data/colors.csv +108 -0
  12. package/skills/hefesto-design/data/components.csv +31 -0
  13. package/skills/hefesto-design/data/google-fonts.csv +56 -0
  14. package/skills/hefesto-design/data/icons.csv +23 -0
  15. package/skills/hefesto-design/data/landing-pages.csv +28 -0
  16. package/skills/hefesto-design/data/products.csv +46 -0
  17. package/skills/hefesto-design/data/spacing.csv +16 -0
  18. package/skills/hefesto-design/data/styles.csv +53 -0
  19. package/skills/hefesto-design/data/typography.csv +41 -0
  20. package/skills/hefesto-design/data/ux-rules.csv +61 -0
  21. package/skills/hefesto-design/references/accessibility.md +335 -0
  22. package/skills/hefesto-design/references/aesthetics.md +343 -0
  23. package/skills/hefesto-design/references/anti-patterns.md +107 -0
  24. package/skills/hefesto-design/references/checklist.md +66 -0
  25. package/skills/hefesto-design/references/color-psychology.md +203 -0
  26. package/skills/hefesto-design/references/component-specs.md +318 -0
  27. package/skills/hefesto-design/references/polish.md +339 -0
  28. package/skills/hefesto-design/references/token-architecture.md +394 -0
  29. package/skills/hefesto-design/references/ux-rules.md +349 -0
  30. package/skills/hefesto-design/scripts/__pycache__/audit.cpython-314.pyc +0 -0
  31. package/skills/hefesto-design/scripts/__pycache__/contrast.cpython-314.pyc +0 -0
  32. package/skills/hefesto-design/scripts/__pycache__/core.cpython-314.pyc +0 -0
  33. package/skills/hefesto-design/scripts/__pycache__/design_system.cpython-314.pyc +0 -0
  34. package/skills/hefesto-design/scripts/__pycache__/search.cpython-314.pyc +0 -0
  35. package/skills/hefesto-design/scripts/__pycache__/validate_tokens.cpython-314.pyc +0 -0
  36. package/skills/hefesto-design/scripts/audit.py +450 -0
  37. package/skills/hefesto-design/scripts/contrast.py +195 -0
  38. package/skills/hefesto-design/scripts/core.py +155 -0
  39. package/skills/hefesto-design/scripts/design_system.py +311 -0
  40. package/skills/hefesto-design/scripts/search.py +235 -0
  41. package/skills/hefesto-design/scripts/validate_tokens.py +274 -0
  42. package/{commands/hefesto/init.md → skills/hefesto-init/SKILL.md} +28 -3
  43. package/{commands/hefesto/new-feature.md → skills/hefesto-new-feature/SKILL.md} +5 -2
  44. package/{commands/hefesto/update.md → skills/hefesto-update/SKILL.md} +6 -3
  45. package/templates/DESIGN.md +137 -0
  46. package/templates/RECON.md +54 -0
  47. package/templates/RESEARCH.md +87 -0
  48. package/templates/STATE.md +1 -1
  49. package/templates/VERDICT.md +52 -0
  50. package/agents/.gitkeep +0 -0
  51. package/commands/hefesto/status.md +0 -40
@@ -0,0 +1,235 @@
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()
@@ -0,0 +1,274 @@
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,5 +1,8 @@
1
1
  ---
2
- description: "Inicializa o Hefesto no projeto atual. Use /hefesto:init para criar a estrutura .hefesto/ e começar a organizar o desenvolvimento."
2
+ name: hefesto-init
3
+ description: "Inicializa o Hefesto no projeto atual. Use /hefesto-init para criar a estrutura .hefesto/ e começar a organizar o desenvolvimento."
4
+ user-invocable: true
5
+ disable-model-invocation: true
3
6
  ---
4
7
 
5
8
  # Hefesto Init
@@ -21,7 +24,8 @@ Inicializa a estrutura `.hefesto/` no projeto atual.
21
24
  ├── ROADMAP.md
22
25
  ├── STATE.md
23
26
  ├── config.json
24
- └── features/
27
+ ├── features/
28
+ └── research/
25
29
  ```
26
30
  4. Preencher PROJECT.md com as respostas do usuário.
27
31
  5. Gerar STATE.md inicial com posição "Inicializando".
@@ -33,13 +37,34 @@ Inicializa a estrutura `.hefesto/` no projeto atual.
33
37
  "project": { "name": "", "language": "pt-BR" },
34
38
  "runtime": "claude",
35
39
  "feature": { "id_prefix": "FEAT", "counter": 0 },
40
+ "research": { "id_prefix": "RES", "counter": 0 },
36
41
  "lifecycle": { "auto_update_state": true }
37
42
  }
38
43
  ```
39
- 8. Informar o usuário que o projeto foi inicializado e sugerir `/hefesto:new-feature` para criar a primeira feature.
44
+ 8. Informar o usuário que o projeto foi inicializado e sugerir `/hefesto-new-feature` para criar a primeira feature.
45
+ 9. Verificar se o MCP Context7 está configurado (checar se `.mcp.json` existe e contém `context7`). Se não estiver, sugerir a instalação:
46
+
47
+ ```
48
+ O Hefesto usa o Context7 para consultar documentação atualizada de bibliotecas
49
+ durante pesquisas. Para habilitar, adicione ao .mcp.json do projeto:
50
+ ```
51
+
52
+ ```json
53
+ {
54
+ "mcpServers": {
55
+ "context7": {
56
+ "command": "npx",
57
+ "args": ["-y", "@upstash/context7-mcp"]
58
+ }
59
+ }
60
+ }
61
+ ```
62
+
63
+ Se o usuário quiser instalar, criar ou atualizar o `.mcp.json` adicionando a entrada do Context7 (preservando outros MCPs existentes). Se não quiser, seguir sem — o Context7 é opcional.
40
64
 
41
65
  ## Notas
42
66
 
43
67
  - Todos os textos gerados devem ser em Português BR.
44
68
  - O config.json deve ser atualizado com o nome do projeto informado pelo usuário.
45
69
  - Se `.hefesto/` já existir e o usuário não quiser reinicializar, abortar sem modificar nada.
70
+ - O Context7 é opcional mas recomendado — melhora a qualidade das pesquisas com docs versionados.
@@ -1,5 +1,8 @@
1
1
  ---
2
- description: "Cria uma nova feature no Hefesto. Use /hefesto:new-feature para definir uma nova feature com visão, fluxo do usuário, requisitos e fases de implementação."
2
+ name: hefesto-new-feature
3
+ description: "Cria uma nova feature no Hefesto. Use /hefesto-new-feature para definir uma nova feature com visão, fluxo do usuário, requisitos e fases de implementação."
4
+ user-invocable: true
5
+ disable-model-invocation: true
3
6
  ---
4
7
 
5
8
  # Hefesto New Feature
@@ -8,7 +11,7 @@ Cria um novo documento de feature em `.hefesto/features/`.
8
11
 
9
12
  ## Pré-requisitos
10
13
 
11
- Verificar se `.hefesto/` existe. Se não existir, sugerir `/hefesto:init` primeiro.
14
+ Verificar se `.hefesto/` existe. Se não existir, sugerir `/hefesto-init` primeiro.
12
15
 
13
16
  ## O que fazer
14
17
 
@@ -1,5 +1,8 @@
1
1
  ---
2
- description: "Atualiza o Hefesto para a versão mais recente. Use /hefesto:update para atualizar commands, skills, hooks e templates sem perder o estado do projeto."
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
3
6
  ---
4
7
 
5
8
  # Hefesto Update
@@ -8,7 +11,7 @@ Atualiza o toolkit Hefesto para a versão mais recente do npm.
8
11
 
9
12
  ## Pré-requisitos
10
13
 
11
- Verificar se `.hefesto/` existe. Se não existir, informar que o projeto não foi inicializado e sugerir `/hefesto:init`.
14
+ Verificar se `.hefesto/` existe. Se não existir, informar que o projeto não foi inicializado e sugerir `/hefesto-init`.
12
15
 
13
16
  ## O que fazer
14
17
 
@@ -26,6 +29,6 @@ Verificar se `.hefesto/` existe. Se não existir, informar que o projeto não fo
26
29
  ## Notas
27
30
 
28
31
  - O installer preserva `.hefesto/` existente (PROJECT.md, STATE.md, ROADMAP.md, features/, config.json).
29
- - O que é atualizado: commands, skills, hooks, templates.
32
+ - O que é atualizado: skills, hooks, templates.
30
33
  - Não usar `--global` a menos que o usuário peça explicitamente.
31
34
  - Se o usuário pedir para atualizar um runtime específico (ex: `--gemini`), passar a flag correspondente.