@lugom.io/hefesto 0.1.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.
File without changes
package/bin/install.js ADDED
@@ -0,0 +1,410 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Hefesto Installer
4
+ // Instala o toolkit .hefesto/ + skills/commands no runtime do projeto.
5
+ // Claude Code first, com suporte a Gemini e Codex.
6
+ // Zero dependências externas.
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import os from 'node:os';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+ const PKG_ROOT = path.resolve(__dirname, '..');
16
+
17
+ // ── ANSI Colors ─────────────────────────────────────────────────────────────
18
+
19
+ const red = '\x1b[31m';
20
+ const dim = '\x1b[2m';
21
+ const reset = '\x1b[0m';
22
+
23
+ const banner = '\n' +
24
+ red + ' ██╗ ██╗███████╗███████╗███████╗███████╗████████╗ ██████╗\n' +
25
+ ' ██║ ██║██╔════╝██╔════╝██╔════╝██╔════╝╚══██╔══╝██╔═══██╗\n' +
26
+ ' ███████║█████╗ █████╗ █████╗ ███████╗ ██║ ██║ ██║\n' +
27
+ ' ██╔══██║██╔══╝ ██╔══╝ ██╔══╝ ╚════██║ ██║ ██║ ██║\n' +
28
+ ' ██║ ██║███████╗██║ ███████╗███████║ ██║ ╚██████╔╝\n' +
29
+ ' ╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝╚══════╝ ╚═╝ ╚═════╝\n' + reset +
30
+ dim + ' Toolkit spec-driven + story-driven para agentes AI\n' + reset;
31
+
32
+ // ── CLI Args ────────────────────────────────────────────────────────────────
33
+
34
+ const args = process.argv.slice(2);
35
+ const hasGlobal = args.includes('--global') || args.includes('-g');
36
+ const hasUninstall = args.includes('--uninstall') || args.includes('-u');
37
+ const hasHelp = args.includes('--help') || args.includes('-h');
38
+
39
+ const hasClaude = args.includes('--claude');
40
+ const hasGemini = args.includes('--gemini');
41
+ const hasCodex = args.includes('--codex');
42
+ const hasAll = args.includes('--all');
43
+
44
+ // ── Help ────────────────────────────────────────────────────────────────────
45
+
46
+ if (hasHelp) {
47
+ console.log(banner);
48
+ console.log(` Uso:
49
+ npx @lugom.io/hefesto [opções]
50
+
51
+ Opções:
52
+ --claude Instala para Claude Code (padrão)
53
+ --gemini Instala para Gemini CLI
54
+ --codex Instala para Codex
55
+ --all Instala para todos os runtimes
56
+ --global, -g Instalação global (~/.claude/, etc.)
57
+ --uninstall, -u Remove o Hefesto
58
+ --help, -h Mostra esta ajuda
59
+
60
+ Exemplos:
61
+ npx @lugom.io/hefesto # Instala para Claude Code (local)
62
+ npx @lugom.io/hefesto --global # Instala para Claude Code (global)
63
+ npx @lugom.io/hefesto --all # Instala para todos os runtimes
64
+ npx @lugom.io/hefesto --uninstall # Remove o Hefesto
65
+ `);
66
+ process.exit(0);
67
+ }
68
+
69
+ // ── Runtime Detection ───────────────────────────────────────────────────────
70
+
71
+ function getSelectedRuntimes() {
72
+ if (hasAll) return ['claude', 'gemini', 'codex'];
73
+ const runtimes = [];
74
+ if (hasClaude) runtimes.push('claude');
75
+ if (hasGemini) runtimes.push('gemini');
76
+ if (hasCodex) runtimes.push('codex');
77
+ // Default: Claude Code
78
+ if (runtimes.length === 0) runtimes.push('claude');
79
+ return runtimes;
80
+ }
81
+
82
+ // ── Path Resolution ─────────────────────────────────────────────────────────
83
+
84
+ function getRuntimeDirName(runtime) {
85
+ if (runtime === 'gemini') return '.gemini';
86
+ if (runtime === 'codex') return '.codex';
87
+ return '.claude';
88
+ }
89
+
90
+ function getGlobalDir(runtime) {
91
+ if (runtime === 'gemini') {
92
+ if (process.env.GEMINI_CONFIG_DIR) return path.resolve(process.env.GEMINI_CONFIG_DIR);
93
+ return path.join(os.homedir(), '.gemini');
94
+ }
95
+ if (runtime === 'codex') {
96
+ if (process.env.CODEX_HOME) return path.resolve(process.env.CODEX_HOME);
97
+ return path.join(os.homedir(), '.codex');
98
+ }
99
+ // Claude Code
100
+ if (process.env.CLAUDE_CONFIG_DIR) return path.resolve(process.env.CLAUDE_CONFIG_DIR);
101
+ return path.join(os.homedir(), '.claude');
102
+ }
103
+
104
+ function getRuntimeDir(runtime) {
105
+ if (hasGlobal) return getGlobalDir(runtime);
106
+ return path.join(process.cwd(), getRuntimeDirName(runtime));
107
+ }
108
+
109
+ function getHefestoDir() {
110
+ if (hasGlobal) return path.join(os.homedir(), '.hefesto');
111
+ return path.join(process.cwd(), '.hefesto');
112
+ }
113
+
114
+ // ── File Operations ─────────────────────────────────────────────────────────
115
+
116
+ function ensureDir(dirPath) {
117
+ if (!fs.existsSync(dirPath)) {
118
+ fs.mkdirSync(dirPath, { recursive: true });
119
+ }
120
+ }
121
+
122
+ function copyDir(src, dest) {
123
+ ensureDir(dest);
124
+ const entries = fs.readdirSync(src, { withFileTypes: true });
125
+ for (const entry of entries) {
126
+ const srcPath = path.join(src, entry.name);
127
+ const destPath = path.join(dest, entry.name);
128
+ if (entry.isDirectory()) {
129
+ copyDir(srcPath, destPath);
130
+ } else {
131
+ fs.copyFileSync(srcPath, destPath);
132
+ }
133
+ }
134
+ }
135
+
136
+ function removeIfExists(targetPath) {
137
+ if (fs.existsSync(targetPath)) {
138
+ fs.rmSync(targetPath, { recursive: true });
139
+ return true;
140
+ }
141
+ return false;
142
+ }
143
+
144
+ // ── Default Config ──────────────────────────────────────────────────────────
145
+
146
+ function createDefaultConfig() {
147
+ return {
148
+ version: '0.1.0',
149
+ project: { name: '', language: 'pt-BR' },
150
+ runtime: 'claude',
151
+ feature: { id_prefix: 'FEAT', counter: 0 },
152
+ lifecycle: { auto_update_state: true },
153
+ };
154
+ }
155
+
156
+ // ── Install ─────────────────────────────────────────────────────────────────
157
+
158
+ function installHefesto() {
159
+ const hefestoDir = getHefestoDir();
160
+ const templatesDir = path.join(PKG_ROOT, 'templates');
161
+
162
+ console.log(banner);
163
+ console.log(` Instalando...\n`);
164
+
165
+ // 1. Criar .hefesto/ com scaffold
166
+ if (fs.existsSync(hefestoDir)) {
167
+ console.log(` ℹ️ .hefesto/ já existe, mantendo estado do projeto.`);
168
+ } else {
169
+ ensureDir(hefestoDir);
170
+ ensureDir(path.join(hefestoDir, 'features'));
171
+
172
+ // Copiar templates como arquivos iniciais do projeto
173
+ const templateFiles = ['PROJECT.md', 'STATE.md', 'ROADMAP.md'];
174
+ for (const file of templateFiles) {
175
+ const src = path.join(templatesDir, file);
176
+ const dest = path.join(hefestoDir, file);
177
+ if (fs.existsSync(src)) {
178
+ fs.copyFileSync(src, dest);
179
+ }
180
+ }
181
+
182
+ // Copiar pasta templates/ inteira para .hefesto/templates/
183
+ copyDir(templatesDir, path.join(hefestoDir, 'templates'));
184
+
185
+ // Criar config.json
186
+ fs.writeFileSync(
187
+ path.join(hefestoDir, 'config.json'),
188
+ JSON.stringify(createDefaultConfig(), null, 2) + '\n',
189
+ );
190
+
191
+ console.log(` ✅ .hefesto/ criado com scaffold do projeto.`);
192
+ }
193
+
194
+ // 2. Instalar em cada runtime
195
+ const runtimes = getSelectedRuntimes();
196
+ for (const runtime of runtimes) {
197
+ installRuntime(runtime);
198
+ }
199
+
200
+ console.log(`\n ✅ Hefesto instalado com sucesso!`);
201
+ console.log(` Use /hefesto:init para configurar o projeto.\n`);
202
+ }
203
+
204
+ function installRuntime(runtime) {
205
+ const runtimeDir = getRuntimeDir(runtime);
206
+ ensureDir(runtimeDir);
207
+
208
+ // Commands
209
+ const srcCommands = path.join(PKG_ROOT, 'commands', 'hefesto');
210
+ if (fs.existsSync(srcCommands)) {
211
+ const destCommands = getCommandsDestDir(runtime, runtimeDir);
212
+ copyDir(srcCommands, destCommands);
213
+ console.log(` ✅ Commands instalados em ${path.relative(process.cwd(), destCommands) || destCommands}`);
214
+ }
215
+
216
+ // Skills
217
+ const srcSkills = path.join(PKG_ROOT, 'skills');
218
+ if (fs.existsSync(srcSkills)) {
219
+ const destSkills = getSkillsDestDir(runtime, runtimeDir);
220
+ copySkills(srcSkills, destSkills);
221
+ console.log(` ✅ Skills instaladas em ${path.relative(process.cwd(), destSkills) || destSkills}`);
222
+ }
223
+
224
+ // Hooks (apenas Claude Code por enquanto)
225
+ if (runtime === 'claude') {
226
+ installHooks(runtimeDir);
227
+ }
228
+ }
229
+
230
+ function getCommandsDestDir(_runtime, runtimeDir) {
231
+ // Claude: .claude/commands/hefesto/
232
+ // Gemini: .gemini/commands/hefesto/
233
+ // Codex: .codex/commands/hefesto/
234
+ return path.join(runtimeDir, 'commands', 'hefesto');
235
+ }
236
+
237
+ function getSkillsDestDir(_runtime, runtimeDir) {
238
+ // Claude: .claude/skills/
239
+ // Gemini: .gemini/skills/
240
+ // Codex: .codex/skills/
241
+ return path.join(runtimeDir, 'skills');
242
+ }
243
+
244
+ function copySkills(srcDir, destDir) {
245
+ ensureDir(destDir);
246
+ const entries = fs.readdirSync(srcDir, { withFileTypes: true });
247
+ for (const entry of entries) {
248
+ if (entry.isDirectory() && entry.name.startsWith('hefesto-')) {
249
+ const srcSkill = path.join(srcDir, entry.name);
250
+ const destSkill = path.join(destDir, entry.name);
251
+ copyDir(srcSkill, destSkill);
252
+ }
253
+ }
254
+ }
255
+
256
+ // ── Hooks ───────────────────────────────────────────────────────────────────
257
+
258
+ function installHooks(runtimeDir) {
259
+ const srcHooks = path.join(PKG_ROOT, 'hooks');
260
+ const destHooks = path.join(runtimeDir, 'hooks');
261
+
262
+ // Copiar hooks
263
+ ensureDir(destHooks);
264
+ for (const file of ['hefesto-statusline.cjs', 'hefesto-check-update.cjs']) {
265
+ const src = path.join(srcHooks, file);
266
+ if (fs.existsSync(src)) {
267
+ fs.copyFileSync(src, path.join(destHooks, file));
268
+ }
269
+ }
270
+ console.log(` ✅ Hooks instalados em ${path.relative(process.cwd(), destHooks) || destHooks}`);
271
+
272
+ // Registrar hooks em settings.json
273
+ const settingsPath = path.join(runtimeDir, 'settings.json');
274
+ let settings = {};
275
+ if (fs.existsSync(settingsPath)) {
276
+ try {
277
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
278
+ } catch (_) {}
279
+ }
280
+
281
+ const hooksDir = destHooks.replace(/\\/g, '/');
282
+ const statuslineCmd = `node "${hooksDir}/hefesto-statusline.cjs"`;
283
+ const checkUpdateCmd = `node "${hooksDir}/hefesto-check-update.cjs"`;
284
+
285
+ // Statusline
286
+ settings.statusLine = {
287
+ type: 'command',
288
+ command: statuslineCmd,
289
+ };
290
+
291
+ // SessionStart hook (check-update)
292
+ if (!settings.hooks) settings.hooks = {};
293
+ if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
294
+
295
+ // Remover hook hefesto anterior se existir
296
+ settings.hooks.SessionStart = settings.hooks.SessionStart.filter(
297
+ h => !JSON.stringify(h).includes('hefesto-check-update'),
298
+ );
299
+
300
+ settings.hooks.SessionStart.push({
301
+ matcher: 'startup',
302
+ hooks: [{
303
+ type: 'command',
304
+ command: checkUpdateCmd,
305
+ }],
306
+ });
307
+
308
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
309
+ console.log(` ✅ Hooks registrados em settings.json`);
310
+ }
311
+
312
+ function uninstallHooks(runtimeDir) {
313
+ // Remover arquivos de hooks
314
+ const hooksDir = path.join(runtimeDir, 'hooks');
315
+ for (const file of ['hefesto-statusline.cjs', 'hefesto-check-update.cjs']) {
316
+ const hookPath = path.join(hooksDir, file);
317
+ if (fs.existsSync(hookPath)) {
318
+ fs.unlinkSync(hookPath);
319
+ }
320
+ }
321
+
322
+ // Limpar settings.json
323
+ const settingsPath = path.join(runtimeDir, 'settings.json');
324
+ if (!fs.existsSync(settingsPath)) return;
325
+
326
+ try {
327
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
328
+
329
+ // Remover statusline se é do hefesto
330
+ if (settings.statusLine?.command?.includes('hefesto-statusline')) {
331
+ delete settings.statusLine;
332
+ }
333
+
334
+ // Remover hook de SessionStart do hefesto
335
+ if (settings.hooks?.SessionStart) {
336
+ settings.hooks.SessionStart = settings.hooks.SessionStart.filter(
337
+ h => !JSON.stringify(h).includes('hefesto-check-update'),
338
+ );
339
+ if (settings.hooks.SessionStart.length === 0) {
340
+ delete settings.hooks.SessionStart;
341
+ }
342
+ }
343
+
344
+ // Limpar hooks vazio
345
+ if (settings.hooks && Object.keys(settings.hooks).length === 0) {
346
+ delete settings.hooks;
347
+ }
348
+
349
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
350
+ console.log(` ✅ Hooks removidos de settings.json`);
351
+ } catch (_) {}
352
+ }
353
+
354
+ // ── Uninstall ───────────────────────────────────────────────────────────────
355
+
356
+ function uninstallHefesto() {
357
+ console.log(banner);
358
+ console.log(` Desinstalando...\n`);
359
+
360
+ // 1. Remover .hefesto/
361
+ const hefestoDir = getHefestoDir();
362
+ if (removeIfExists(hefestoDir)) {
363
+ console.log(` ✅ .hefesto/ removido.`);
364
+ } else {
365
+ console.log(` ℹ️ .hefesto/ não encontrado.`);
366
+ }
367
+
368
+ // 2. Remover de cada runtime
369
+ const runtimes = getSelectedRuntimes();
370
+ for (const runtime of runtimes) {
371
+ uninstallRuntime(runtime);
372
+ }
373
+
374
+ console.log(`\n ✅ Hefesto desinstalado.\n`);
375
+ }
376
+
377
+ function uninstallRuntime(runtime) {
378
+ const runtimeDir = getRuntimeDir(runtime);
379
+
380
+ // Remover commands/hefesto/
381
+ const commandsDir = getCommandsDestDir(runtime, runtimeDir);
382
+ if (removeIfExists(commandsDir)) {
383
+ console.log(` ✅ Commands removidos de ${path.relative(process.cwd(), commandsDir) || commandsDir}`);
384
+ }
385
+
386
+ // Remover skills hefesto-*
387
+ const skillsDir = getSkillsDestDir(runtime, runtimeDir);
388
+ if (fs.existsSync(skillsDir)) {
389
+ const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
390
+ for (const entry of entries) {
391
+ if (entry.isDirectory() && entry.name.startsWith('hefesto-')) {
392
+ fs.rmSync(path.join(skillsDir, entry.name), { recursive: true });
393
+ console.log(` ✅ Skill ${entry.name} removida.`);
394
+ }
395
+ }
396
+ }
397
+
398
+ // Remover hooks (apenas Claude Code)
399
+ if (runtime === 'claude') {
400
+ uninstallHooks(runtimeDir);
401
+ }
402
+ }
403
+
404
+ // ── Main ────────────────────────────────────────────────────────────────────
405
+
406
+ if (hasUninstall) {
407
+ uninstallHefesto();
408
+ } else {
409
+ installHefesto();
410
+ }
@@ -0,0 +1,45 @@
1
+ ---
2
+ description: "Inicializa o Hefesto no projeto atual. Use /hefesto:init para criar a estrutura .hefesto/ e começar a organizar o desenvolvimento."
3
+ ---
4
+
5
+ # Hefesto Init
6
+
7
+ Inicializa a estrutura `.hefesto/` no projeto atual.
8
+
9
+ ## O que fazer
10
+
11
+ 1. Verificar se `.hefesto/` já existe. Se existir, avisar o usuário e perguntar se quer reinicializar.
12
+ 2. Perguntar ao usuário:
13
+ - Nome do projeto
14
+ - Descrição curta (2-3 frases)
15
+ - Valor central (a única coisa que DEVE funcionar)
16
+ - Restrições principais (stack, prazo, plataforma, etc.)
17
+ 3. Criar a estrutura de diretórios:
18
+ ```
19
+ .hefesto/
20
+ ├── PROJECT.md
21
+ ├── ROADMAP.md
22
+ ├── STATE.md
23
+ ├── config.json
24
+ └── features/
25
+ ```
26
+ 4. Preencher PROJECT.md com as respostas do usuário.
27
+ 5. Gerar STATE.md inicial com posição "Inicializando".
28
+ 6. Gerar ROADMAP.md vazio.
29
+ 7. Gerar config.json com valores padrão:
30
+ ```json
31
+ {
32
+ "version": "0.1.0",
33
+ "project": { "name": "", "language": "pt-BR" },
34
+ "runtime": "claude",
35
+ "feature": { "id_prefix": "FEAT", "counter": 0 },
36
+ "lifecycle": { "auto_update_state": true }
37
+ }
38
+ ```
39
+ 8. Informar o usuário que o projeto foi inicializado e sugerir `/hefesto:new-feature` para criar a primeira feature.
40
+
41
+ ## Notas
42
+
43
+ - Todos os textos gerados devem ser em Português BR.
44
+ - O config.json deve ser atualizado com o nome do projeto informado pelo usuário.
45
+ - Se `.hefesto/` já existir e o usuário não quiser reinicializar, abortar sem modificar nada.
@@ -0,0 +1,50 @@
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."
3
+ ---
4
+
5
+ # Hefesto New Feature
6
+
7
+ Cria um novo documento de feature em `.hefesto/features/`.
8
+
9
+ ## Pré-requisitos
10
+
11
+ Verificar se `.hefesto/` existe. Se não existir, sugerir `/hefesto:init` primeiro.
12
+
13
+ ## O que fazer
14
+
15
+ 1. Ler `.hefesto/templates/FEATURE.md` como base para o novo arquivo de feature.
16
+ 2. Ler `.hefesto/config.json` para obter o próximo ID disponível.
17
+ 3. Perguntar ao usuário:
18
+ - Título da feature (curto, descritivo)
19
+ - Visão (o que entrega e por que existe)
20
+ - Fluxo do usuário (passos que o usuário percorre)
21
+ - Requisitos principais (testáveis, centrados no usuário)
22
+ - O que está fora do escopo
23
+ 4. Com base nas respostas, propor fases de implementação:
24
+ - Cada fase deve ser atômica (executável em uma sessão)
25
+ - Listar arquivos que serão criados/modificados
26
+ - Definir tarefas concretas e critérios de aceitação
27
+ 5. Gerar o ID: `FEAT-NNN` onde NNN é o counter + 1, zero-padded.
28
+ 6. Gerar o slug a partir do título (lowercase, hifenizado, max 40 chars).
29
+ 7. Criar o arquivo `.hefesto/features/FEAT-NNN-slug.md` com o conteúdo.
30
+ 8. Atualizar `.hefesto/config.json` incrementando o counter.
31
+ 9. Atualizar `.hefesto/ROADMAP.md` adicionando a nova feature na tabela.
32
+ 10. Atualizar `.hefesto/STATE.md` se for a primeira feature ou se nenhuma estiver ativa.
33
+
34
+ ## Formato do arquivo
35
+
36
+ O arquivo segue o template narrativo unificado com frontmatter YAML:
37
+
38
+ ```yaml
39
+ ---
40
+ id: FEAT-NNN
41
+ title: "Título"
42
+ status: draft
43
+ created: YYYY-MM-DD
44
+ updated: YYYY-MM-DD
45
+ phases_total: N
46
+ phases_done: 0
47
+ ---
48
+ ```
49
+
50
+ Seguido pelas seções: Visão, Fluxo do Usuário, Requisitos, Fora do Escopo, Implementação (com Fases), Notas Técnicas.
@@ -0,0 +1,40 @@
1
+ ---
2
+ description: "Mostra o estado atual do projeto Hefesto. Use /hefesto:status para ver progresso, features ativas, bloqueios e próximos passos."
3
+ ---
4
+
5
+ # Hefesto Status
6
+
7
+ Exibe o estado atual do projeto gerenciado pelo Hefesto.
8
+
9
+ ## Pré-requisitos
10
+
11
+ Verificar se `.hefesto/` existe. Se não existir, informar que o projeto não foi inicializado e sugerir `/hefesto:init`.
12
+
13
+ ## O que fazer
14
+
15
+ 1. Ler `.hefesto/STATE.md` e exibir o conteúdo formatado.
16
+ 2. Ler `.hefesto/ROADMAP.md` e calcular progresso geral.
17
+ 3. Listar todas as features em `.hefesto/features/` com seus status (ler frontmatter de cada arquivo).
18
+ 4. Apresentar um resumo ao usuário:
19
+
20
+ ```
21
+ 📊 Estado do Projeto: [nome]
22
+
23
+ Progresso: [██████░░░░] 60% (3 de 5 features)
24
+
25
+ Feature ativa: FEAT-003 — Título (Fase 2 de 4)
26
+
27
+ Features:
28
+ ✅ FEAT-001 — Título (done)
29
+ ✅ FEAT-002 — Título (done)
30
+ 🔄 FEAT-003 — Título (active — fase 2/4)
31
+ 📋 FEAT-004 — Título (ready)
32
+ 📝 FEAT-005 — Título (draft)
33
+
34
+ Bloqueios: Nenhum
35
+
36
+ Próximo passo: Continuar execução da Fase 2 de FEAT-003
37
+ ```
38
+
39
+ 5. Se houver bloqueios em STATE.md, destacá-los.
40
+ 6. Sugerir próxima ação com base no estado atual.
@@ -0,0 +1,31 @@
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."
3
+ ---
4
+
5
+ # Hefesto Update
6
+
7
+ Atualiza o toolkit Hefesto para a versão mais recente do npm.
8
+
9
+ ## Pré-requisitos
10
+
11
+ Verificar se `.hefesto/` existe. Se não existir, informar que o projeto não foi inicializado e sugerir `/hefesto:init`.
12
+
13
+ ## O que fazer
14
+
15
+ 1. Ler `.hefesto/config.json` e anotar a versão atual (`version`).
16
+ 2. Executar o comando de atualização:
17
+ ```bash
18
+ npx @lugom.io/hefesto@latest
19
+ ```
20
+ 3. Verificar o output do comando. Se houver erro, informar o usuário e sugerir ação corretiva.
21
+ 4. Ler `.hefesto/config.json` novamente e comparar a versão com a anterior.
22
+ 5. Informar o resultado ao usuário:
23
+ - Se atualizou: mostrar versão anterior → nova versão
24
+ - Se já estava na última versão: informar que está atualizado
25
+
26
+ ## Notas
27
+
28
+ - O installer preserva `.hefesto/` existente (PROJECT.md, STATE.md, ROADMAP.md, features/, config.json).
29
+ - O que é atualizado: commands, skills, hooks, templates.
30
+ - Não usar `--global` a menos que o usuário peça explicitamente.
31
+ - Se o usuário pedir para atualizar um runtime específico (ex: `--gemini`), passar a flag correspondente.
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ // Hefesto Update Checker
3
+ // Verifica novas versões em background no SessionStart
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+ const { spawn } = require('child_process');
9
+
10
+ const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
11
+ const cacheDir = path.join(configDir, 'cache');
12
+ const cacheFile = path.join(cacheDir, 'hefesto-update-check.json');
13
+
14
+ // Garantir que cache dir existe
15
+ if (!fs.existsSync(cacheDir)) {
16
+ fs.mkdirSync(cacheDir, { recursive: true });
17
+ }
18
+
19
+ // Rodar check em background (não bloqueia a sessão)
20
+ const child = spawn(process.execPath, ['-e', `
21
+ const fs = require('fs');
22
+ const { execSync } = require('child_process');
23
+ const cacheFile = ${JSON.stringify(cacheFile)};
24
+
25
+ let installed = '0.0.0';
26
+ try {
27
+ const pkg = JSON.parse(execSync('npm ls hefesto --json 2>/dev/null', {
28
+ encoding: 'utf8', timeout: 10000, windowsHide: true
29
+ }));
30
+ installed = pkg.dependencies?.hefesto?.version || '0.0.0';
31
+ } catch (_) {
32
+ // Se não conseguir detectar versão instalada, tentar package.json local
33
+ try {
34
+ const localPkg = JSON.parse(fs.readFileSync('node_modules/hefesto/package.json', 'utf8'));
35
+ installed = localPkg.version || '0.0.0';
36
+ } catch (_) {}
37
+ }
38
+
39
+ let latest = null;
40
+ try {
41
+ latest = execSync('npm view hefesto version', {
42
+ encoding: 'utf8', timeout: 10000, windowsHide: true
43
+ }).trim();
44
+ } catch (_) {}
45
+
46
+ const result = {
47
+ update_available: latest && installed !== latest && latest !== '0.0.0',
48
+ installed,
49
+ latest: latest || 'unknown',
50
+ checked: Math.floor(Date.now() / 1000),
51
+ };
52
+
53
+ fs.writeFileSync(cacheFile, JSON.stringify(result));
54
+ `], {
55
+ stdio: 'ignore',
56
+ windowsHide: true,
57
+ detached: true,
58
+ });
59
+
60
+ child.unref();
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+ // Hefesto Statusline
3
+ // Mostra: ⚒ HEFESTO | model | directory | context usage
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+
9
+ let input = '';
10
+ const stdinTimeout = setTimeout(() => process.exit(0), 3000);
11
+ process.stdin.setEncoding('utf8');
12
+ process.stdin.on('data', chunk => input += chunk);
13
+ process.stdin.on('end', () => {
14
+ clearTimeout(stdinTimeout);
15
+ try {
16
+ const data = JSON.parse(input);
17
+ const model = data.model?.display_name || 'Claude';
18
+ const dir = data.workspace?.current_dir || process.cwd();
19
+ const session = data.session_id || '';
20
+ const remaining = data.context_window?.remaining_percentage;
21
+
22
+ // Context window (normalizado para contexto utilizável)
23
+ const AUTO_COMPACT_BUFFER_PCT = 16.5;
24
+ let ctx = '';
25
+ if (remaining != null) {
26
+ const usableRemaining = Math.max(0, ((remaining - AUTO_COMPACT_BUFFER_PCT) / (100 - AUTO_COMPACT_BUFFER_PCT)) * 100);
27
+ const used = Math.max(0, Math.min(100, Math.round(100 - usableRemaining)));
28
+
29
+ // Bridge file para o context-monitor
30
+ if (session) {
31
+ try {
32
+ const bridgePath = path.join(os.tmpdir(), `hefesto-ctx-${session}.json`);
33
+ fs.writeFileSync(bridgePath, JSON.stringify({
34
+ session_id: session,
35
+ remaining_percentage: remaining,
36
+ used_pct: used,
37
+ timestamp: Math.floor(Date.now() / 1000),
38
+ }));
39
+ } catch (_) {}
40
+ }
41
+
42
+ // Barra de progresso (10 segmentos)
43
+ const filled = Math.floor(used / 10);
44
+ const bar = '█'.repeat(filled) + '░'.repeat(10 - filled);
45
+
46
+ if (used < 50) {
47
+ ctx = ` \x1b[32m${bar} ${used}%\x1b[0m`;
48
+ } else if (used < 65) {
49
+ ctx = ` \x1b[33m${bar} ${used}%\x1b[0m`;
50
+ } else if (used < 80) {
51
+ ctx = ` \x1b[38;5;208m${bar} ${used}%\x1b[0m`;
52
+ } else {
53
+ ctx = ` \x1b[5;31m💀 ${bar} ${used}%\x1b[0m`;
54
+ }
55
+ }
56
+
57
+ // Update disponível?
58
+ let updateMsg = '';
59
+ const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
60
+ const cacheFile = path.join(configDir, 'cache', 'hefesto-update-check.json');
61
+ if (fs.existsSync(cacheFile)) {
62
+ try {
63
+ const cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
64
+ if (cache.update_available) {
65
+ updateMsg = '\x1b[33m⬆ npx @lugom.io/hefesto\x1b[0m │ ';
66
+ }
67
+ } catch (_) {}
68
+ }
69
+
70
+ const dirname = path.basename(dir);
71
+ process.stdout.write(`${updateMsg}\x1b[1;31m⚒ HEFESTO\x1b[0m │ \x1b[2m${model}\x1b[0m │ \x1b[2m${dirname}\x1b[0m${ctx}`);
72
+ } catch (_) {
73
+ // Falha silenciosa
74
+ }
75
+ });
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@lugom.io/hefesto",
3
+ "version": "0.1.0",
4
+ "description": "A meta-prompting, context engineering and spec-driven development system for Claude Code, Gemini and Codex by lugom.io.",
5
+ "type": "module",
6
+ "bin": {
7
+ "hefesto": "./bin/install.js"
8
+ },
9
+ "scripts": {
10
+ "test": "node --test tests/**/*.test.js"
11
+ },
12
+ "engines": {
13
+ "node": ">=18"
14
+ },
15
+ "files": [
16
+ "bin/",
17
+ "templates/",
18
+ "commands/",
19
+ "skills/",
20
+ "hooks/",
21
+ "agents/"
22
+ ],
23
+ "keywords": [
24
+ "claude",
25
+ "claude-code",
26
+ "ai",
27
+ "meta-prompting",
28
+ "context-engineering",
29
+ "spec-driven-development",
30
+ "gemini",
31
+ "gemini-cli",
32
+ "codex",
33
+ "codex-cli"
34
+ ],
35
+ "author": "lugom.io",
36
+ "license": "MIT"
37
+ }
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: hefesto-context
3
+ description: Conhecimento sobre a estrutura e convenções do Hefesto. Use sempre que o projeto tiver um diretório .hefesto/, quando o usuário mencionar features, specs, stories, roadmap ou estado do projeto no contexto do Hefesto, ou quando precisar entender como o .hefesto/ organiza o desenvolvimento.
4
+ ---
5
+
6
+ # Hefesto Context
7
+
8
+ O Hefesto é um toolkit spec-driven + story-driven que organiza o desenvolvimento através de documentos narrativos unificados chamados **features**.
9
+
10
+ ## Estrutura `.hefesto/`
11
+
12
+ ```
13
+ .hefesto/
14
+ ├── PROJECT.md # Visão, valor central, restrições do projeto
15
+ ├── ROADMAP.md # Lista de features com status e progresso
16
+ ├── STATE.md # Memória viva — posição atual, decisões, bloqueios (~80 linhas)
17
+ ├── config.json # Configuração (counters de ID, runtime, preferências)
18
+ └── features/ # Documentos narrativos unificados
19
+ └── FEAT-NNN-slug.md # Uma feature por arquivo
20
+ ```
21
+
22
+ ## Features
23
+
24
+ Cada feature é um documento narrativo que combina **spec** (O QUÊ) e **stories** (COMO) em um único arquivo:
25
+
26
+ - **Visão** — o que entrega e por que existe
27
+ - **Fluxo do Usuário** — passos que o usuário percorre
28
+ - **Requisitos** — checklist testável (REQ-01, REQ-02, ...)
29
+ - **Fora do Escopo** — exclusões explícitas
30
+ - **Implementação** — dividida em **Fases** (cada fase = uma story atômica)
31
+ - Arquivos a criar/modificar
32
+ - Tarefas concretas
33
+ - Critérios de aceitação
34
+
35
+ ## IDs e Naming
36
+
37
+ - **Feature ID**: `FEAT-NNN` (zero-padded, sequencial)
38
+ - **Arquivo**: `FEAT-NNN-slug.md` (slug = título lowercase hifenizado, max 40 chars)
39
+ - **Counter**: mantido em `config.json` → `feature.counter`
40
+
41
+ ## Status
42
+
43
+ - `draft` — em definição
44
+ - `ready` — pronta para execução
45
+ - `active` — em andamento
46
+ - `done` — completa e verificada
47
+ - `blocked` — bloqueada por dependência
48
+
49
+ ## STATE.md
50
+
51
+ Memória viva do projeto. Máximo ~80 linhas. Contém:
52
+ - Posição atual (features feitas vs total)
53
+ - Feature e fase ativa
54
+ - Decisões recentes
55
+ - Bloqueios
56
+ - Informação de continuidade (última sessão, onde parou, como retomar)
57
+
58
+ ## Outputs
59
+
60
+ Outputs reais (código, documentos, manifestos) vão para o projeto (src/, docs/, etc.). O `.hefesto/` apenas rastreia o que foi produzido e onde foi colocado.
61
+
62
+ ## Commands disponíveis
63
+
64
+ - `/hefesto:init` — Inicializa .hefesto/ no projeto
65
+ - `/hefesto:new-feature` — Cria nova feature
66
+ - `/hefesto:status` — Mostra estado do projeto
@@ -0,0 +1,52 @@
1
+ ---
2
+ id: { { FEATURE_ID } }
3
+ title: "{{FEATURE_TITLE}}"
4
+ status: draft
5
+ created: { { DATE } }
6
+ updated: { { DATE } }
7
+ phases_total: 0
8
+ phases_done: 0
9
+ ---
10
+
11
+ # {{FEATURE_ID}}: {{FEATURE_TITLE}}
12
+
13
+ ## Visão
14
+
15
+ {{FEATURE_VISION}}
16
+
17
+ ## Fluxo do Usuário
18
+
19
+ 1. {{USER_FLOW_STEP_1}}
20
+ 2. {{USER_FLOW_STEP_2}}
21
+ 3. {{USER_FLOW_STEP_3}}
22
+
23
+ ## Requisitos
24
+
25
+ - [ ] **REQ-01**: {{REQUIREMENT_1}}
26
+ - [ ] **REQ-02**: {{REQUIREMENT_2}}
27
+
28
+ ## Fora do Escopo
29
+
30
+ - {{EXCLUSION_1}}
31
+
32
+ ## Implementação
33
+
34
+ ### Fase 1: {{PHASE_1_NAME}}
35
+
36
+ **Arquivos:**
37
+
38
+ - `{{FILE_PATH}}` — {{FILE_DESCRIPTION}}
39
+
40
+ **Tarefas:**
41
+
42
+ - [ ] {{TASK_1}}
43
+ - [ ] {{TASK_2}}
44
+
45
+ **Critérios de Aceitação:**
46
+
47
+ - [ ] {{ACCEPTANCE_CRITERIA_1}}
48
+ - [ ] {{ACCEPTANCE_CRITERIA_2}}
49
+
50
+ ## Notas Técnicas
51
+
52
+ {{TECHNICAL_NOTES}}
@@ -0,0 +1,28 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ ## O Que É
4
+
5
+ {{PROJECT_DESCRIPTION}}
6
+
7
+ ## Valor Central
8
+
9
+ {{CORE_VALUE}}
10
+
11
+ ## Contexto
12
+
13
+ {{CONTEXT}}
14
+
15
+ ## Restrições
16
+
17
+ - **Stack**: {{STACK_CONSTRAINT}}
18
+ - **Prazo**: {{DEADLINE_CONSTRAINT}}
19
+
20
+ ## Decisões
21
+
22
+ | Decisão | Justificativa | Resultado |
23
+ | ------- | ------------- | --------- |
24
+ | — | — | — |
25
+
26
+ ---
27
+
28
+ _Última atualização: {{DATE}}_
@@ -0,0 +1,23 @@
1
+ # Roadmap
2
+
3
+ ## Visão Geral
4
+
5
+ {{PROJECT_NAME}} — {{PROJECT_DESCRIPTION}}
6
+
7
+ ## Features
8
+
9
+ | ID | Título | Status | Fases | Progresso |
10
+ | --- | ------ | ------ | ----- | --------- |
11
+ | — | — | — | — | — |
12
+
13
+ ## Legenda
14
+
15
+ - `draft` — Em definição
16
+ - `ready` — Pronta para execução
17
+ - `active` — Em andamento
18
+ - `done` — Completa e verificada
19
+ - `blocked` — Bloqueada
20
+
21
+ ---
22
+
23
+ _Atualizado automaticamente ao concluir features._
@@ -0,0 +1,37 @@
1
+ # Estado do Projeto
2
+
3
+ ## Referência
4
+
5
+ **Projeto:** {{PROJECT_NAME}}
6
+ **Valor central:** {{CORE_VALUE}}
7
+ **Foco atual:** {{CURRENT_FOCUS}}
8
+
9
+ ## Posição Atual
10
+
11
+ - Features: 0 de 0 completas
12
+ - Status: Inicializando
13
+ - Última atividade: {{DATE}} — Projeto inicializado
14
+
15
+ Progresso: [░░░░░░░░░░] 0%
16
+
17
+ ## Feature Ativa
18
+
19
+ Nenhuma.
20
+
21
+ ## Decisões Recentes
22
+
23
+ Nenhuma.
24
+
25
+ ## Bloqueios
26
+
27
+ Nenhum.
28
+
29
+ ## Continuidade
30
+
31
+ - Última sessão: {{DATE}}
32
+ - Parou em: Inicialização do projeto
33
+ - Retomar: Criar primeira feature com /hefesto:new-feature
34
+
35
+ ---
36
+
37
+ _Manter abaixo de 80 linhas. É um resumo, não um arquivo._