@spec-wave/cli 0.5.7 → 0.5.9
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/README.md +390 -0
- package/bin/spec-wave.mjs +14 -0
- package/package.json +1 -1
- package/src/commands/implement.mjs +56 -29
- package/src/commands/info.mjs +3 -1
- package/src/commands/init.mjs +27 -21
- package/src/commands/install-skill.mjs +309 -0
- package/src/config.mjs +11 -0
- package/src/templates/skill/SKILL.md +520 -0
package/README.md
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
# @spec-wave/cli
|
|
2
|
+
|
|
3
|
+
CLI e skill para implementar um fluxo **spec-driven** completo no GitHub — do backlog ao deploy — com GitHub Projects v2, labels de gatilho e GitHub Actions com IA.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Conceito
|
|
8
|
+
|
|
9
|
+
Spec Wave é um sistema de processo de desenvolvimento baseado em especificações. Cada Feature passa por um ciclo documentado antes de ser implementada:
|
|
10
|
+
|
|
11
|
+
1. **Spec funcional** gerada por IA a partir do título e descrição da issue
|
|
12
|
+
2. **Plano técnico** gerado por IA a partir da spec e do contexto tecnológico do repositório
|
|
13
|
+
3. **Validação automática** das seções obrigatórias
|
|
14
|
+
4. **Decomposição em Stories e Tasks** gerada por IA
|
|
15
|
+
5. **Automação do board** durante o ciclo de desenvolvimento (Code Review → QA → Done)
|
|
16
|
+
|
|
17
|
+
O resultado é um board Kanban no GitHub Projects v2 que avança automaticamente conforme o trabalho progride, com toda a documentação versionada no próprio repositório.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Fluxo Kanban
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
📥 Backlog
|
|
25
|
+
→ 🎯 Priorizado
|
|
26
|
+
→ 📋 Spec ← label spec-wave:spec → Action gera spec.md
|
|
27
|
+
→ 📋 Plan ← label spec-wave:plan → Action gera plan.md
|
|
28
|
+
→ ✅ Ready ← label spec-wave:ready → Action valida ambos
|
|
29
|
+
→ 📋 Backlog Técnico
|
|
30
|
+
→ 🚧 Desenvolvimento ← comando local: spec-wave implement <n>
|
|
31
|
+
→ 👀 Code Review ← PR aberto → Action move automaticamente
|
|
32
|
+
→ 🧪 QA ← PR aprovado → Action move automaticamente
|
|
33
|
+
→ 📋 Homologação
|
|
34
|
+
→ 🚀 Deploy
|
|
35
|
+
→ 🎉 Done
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Hierarquia de Work Items
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
Initiative
|
|
44
|
+
└── Epic
|
|
45
|
+
└── Feature
|
|
46
|
+
├── Story
|
|
47
|
+
│ └── Task
|
|
48
|
+
└── Task
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Cada nível é uma GitHub Issue com prefixo no título (`[FEATURE]`, `[STORY]`, etc.) e vínculo de sub-issue nativo do GitHub.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Componentes
|
|
56
|
+
|
|
57
|
+
### CLI (`@spec-wave/cli`)
|
|
58
|
+
|
|
59
|
+
Ferramenta Node.js que configura e opera o fluxo via linha de comando.
|
|
60
|
+
|
|
61
|
+
| Comando | O que faz |
|
|
62
|
+
|---------|-----------|
|
|
63
|
+
| `init` | Cria o GitHub Project, labels, workflows e `.spec-wave.json` |
|
|
64
|
+
| `info` | Mostra o estado de configuração do repositório atual |
|
|
65
|
+
| `refresh` | Re-sincroniza o `.spec-wave.json` com o GitHub Project |
|
|
66
|
+
| `issue` | Cria qualquer work item (initiative/epic/feature/story/task/bug/spike/rfc) |
|
|
67
|
+
| `initiative` | Atalho para `issue --type initiative` |
|
|
68
|
+
| `feature` | Atalho para `issue --type feature` |
|
|
69
|
+
| `generate-spec` | Gera `spec.md` (usado pelo GitHub Action) |
|
|
70
|
+
| `generate-plan` | Gera `plan.md` (usado pelo GitHub Action) |
|
|
71
|
+
| `validate` | Valida spec.md e plan.md (usado pelo GitHub Action) |
|
|
72
|
+
| `decompose` | Decompõe Feature em Stories e Tasks (usado pelo GitHub Action) |
|
|
73
|
+
| `code-review` | Move Feature para Code Review ao abrir PR (usado pelo GitHub Action) |
|
|
74
|
+
| `qa` | Move Feature para QA ao aprovar PR (usado pelo GitHub Action) |
|
|
75
|
+
| `implement` | Aciona o spec-kit localmente para implementar uma Story ou Task |
|
|
76
|
+
| `uninstall` | Remove labels, workflows e `.spec-wave.json` |
|
|
77
|
+
|
|
78
|
+
### GitHub Actions (instalados pelo `init`)
|
|
79
|
+
|
|
80
|
+
| Workflow | Gatilho | Ação |
|
|
81
|
+
|----------|---------|------|
|
|
82
|
+
| `generate-spec.yml` | label `spec-wave:spec` | Gera `docs/features/<slug>/spec.md` via IA |
|
|
83
|
+
| `generate-plan.yml` | label `spec-wave:plan` | Gera `docs/features/<slug>/plan.md` via IA |
|
|
84
|
+
| `validate.yml` | label `spec-wave:ready` | Valida seções obrigatórias; adiciona `spec-wave:plan-approved` |
|
|
85
|
+
| `decompose.yml` | label `spec-wave:decompose` | Cria Stories e Tasks como sub-issues |
|
|
86
|
+
| `code-review.yml` | PR aberto/reaberto | Move Feature para `👀 Code Review` |
|
|
87
|
+
| `qa.yml` | PR aprovado | Move Feature para `🧪 QA` |
|
|
88
|
+
|
|
89
|
+
### Skill (`src/templates/skill/SKILL.md`)
|
|
90
|
+
|
|
91
|
+
Skill que guia o usuário pelo fluxo via comandos como `/spec-wave spec 42`, `/spec-wave plan 42`, `/spec-wave decompose 42`. A skill lê o `.spec-wave.json` local, detecta o estado atual e executa os comandos corretos sem abrir wizards interativos. Instale-a no seu agente com `install-skill` (ver abaixo).
|
|
92
|
+
|
|
93
|
+
### `.spec-wave.json`
|
|
94
|
+
|
|
95
|
+
Arquivo de configuração gerado pelo `init` na raiz do repositório. Armazena `owner/repo`, dados do GitHub Project (ID, URL, campos) e o provider de IA configurado. Todos os comandos leem este arquivo para operar sem precisar de flags adicionais.
|
|
96
|
+
|
|
97
|
+
### `tech_context.yml`
|
|
98
|
+
|
|
99
|
+
Arquivo em `.github/config/tech_context.yml` que descreve a stack tecnológica do sistema (backend, frontend, banco, infra, roles RBAC, schemas, serviços). O `generate-plan` usa este arquivo para embasar o plano técnico — sem ele, o plano fica genérico.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Pré-requisitos
|
|
104
|
+
|
|
105
|
+
- Node.js >= 20
|
|
106
|
+
- GitHub CLI (`gh`) autenticado com escopos `project`, `repo` e `workflow`:
|
|
107
|
+
```bash
|
|
108
|
+
gh auth refresh --scopes project,repo,workflow
|
|
109
|
+
```
|
|
110
|
+
- Secret no repositório: `ANTHROPIC_API_KEY` ou `OPENROUTER_API_KEY` (Settings → Secrets → Actions)
|
|
111
|
+
- Para repositórios em organizações: criar PAT com escopo `project` e adicionar como secret `GH_PROJECT_TOKEN`
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Instalação da CLI
|
|
116
|
+
|
|
117
|
+
Não é necessário instalar globalmente — use `npx`:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npx @spec-wave/cli --help
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Para instalar globalmente:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
npm install -g @spec-wave/cli
|
|
127
|
+
spec-wave --help
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Instalação da Skill
|
|
133
|
+
|
|
134
|
+
A skill permite usar o fluxo diretamente no seu agente via `/spec-wave`.
|
|
135
|
+
|
|
136
|
+
**1. Instale a skill com o comando `install-skill`:**
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Autodetecta o agente em uso e instala no local/formato correto
|
|
140
|
+
npx @spec-wave/cli install-skill
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
O comando suporta Claude Code, Cursor, opencode, Cline, Kilo Code, Antigravity e o
|
|
144
|
+
padrão genérico `AGENTS.md`. Por padrão instala no escopo do projeto (versionável
|
|
145
|
+
com o time); use `--global` para o escopo do usuário. Escolha alvos com
|
|
146
|
+
`--agent <nomes>` (ex.: `--agent claude,cursor`) ou `--all` para todos os
|
|
147
|
+
detectados. Use `--dry-run` para pré-visualizar sem gravar.
|
|
148
|
+
|
|
149
|
+
**2. Adicione ao `CLAUDE.md` (ou equivalente) do projeto:**
|
|
150
|
+
|
|
151
|
+
```markdown
|
|
152
|
+
# spec-wave skill
|
|
153
|
+
Trigger `/spec-wave` to invoke the spec-wave skill.
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**3. Use no seu agente:**
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
/spec-wave setup
|
|
160
|
+
/spec-wave spec 42
|
|
161
|
+
/spec-wave plan 42
|
|
162
|
+
/spec-wave ready 42
|
|
163
|
+
/spec-wave decompose 42
|
|
164
|
+
/spec-wave implement 45
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Exemplo de uso — do início ao fim
|
|
170
|
+
|
|
171
|
+
### Contexto
|
|
172
|
+
|
|
173
|
+
Equipe quer implementar uma feature de "Checkout com PIX" em um repositório `acme/loja`.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
### 1. Configurar o repositório
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
# Verificar autenticação
|
|
181
|
+
gh auth status
|
|
182
|
+
|
|
183
|
+
# Se faltarem escopos:
|
|
184
|
+
gh auth refresh --scopes project,repo,workflow
|
|
185
|
+
|
|
186
|
+
# Configurar spec-wave (cria Project, labels e workflows)
|
|
187
|
+
npx @spec-wave/cli init --repo acme/loja --project-title "Loja — Spec Wave"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
O `init` cria:
|
|
191
|
+
- GitHub Project v2 com 12 colunas Kanban e campos personalizados (Work Item Type, Priority, Story Points, Area)
|
|
192
|
+
- 20+ labels de tipo, prioridade e gatilho
|
|
193
|
+
- 6 GitHub Actions workflows em `.github/workflows/`
|
|
194
|
+
- `.spec-wave.json` com os IDs do Project
|
|
195
|
+
|
|
196
|
+
Adicionar o secret de IA no GitHub: **Settings → Secrets → Actions → `ANTHROPIC_API_KEY`**.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
### 2. Criar a hierarquia de issues
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Criar Epic
|
|
204
|
+
npx @spec-wave/cli issue \
|
|
205
|
+
--type epic \
|
|
206
|
+
--title "Checkout e Pagamentos" \
|
|
207
|
+
--priority P1 \
|
|
208
|
+
--area Backend
|
|
209
|
+
# → Issue #5 criada: [EPIC] Checkout e Pagamentos
|
|
210
|
+
|
|
211
|
+
# Criar Feature como sub-issue do Epic
|
|
212
|
+
npx @spec-wave/cli feature \
|
|
213
|
+
--title "Checkout com PIX" \
|
|
214
|
+
--parent 5 \
|
|
215
|
+
--priority P1 \
|
|
216
|
+
--area Backend
|
|
217
|
+
# → Issue #12 criada: [FEATURE] Checkout com PIX (sub-issue de #5)
|
|
218
|
+
# → Adicionada ao board em 📥 Backlog
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
### 3. Gerar a especificação funcional
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
gh issue edit 12 --add-label "spec-wave:spec"
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
O GitHub Action `generate-spec.yml` dispara, chama a IA e faz commit de:
|
|
230
|
+
```
|
|
231
|
+
docs/features/checkout-com-pix/spec.md
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
A issue #12 recebe um comentário com o link para o arquivo.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
### 4. Gerar o plano técnico
|
|
239
|
+
|
|
240
|
+
Antes de gerar o plano, garanta que `.github/config/tech_context.yml` existe e reflete a stack real. O `init` cria um scaffold — edite-o:
|
|
241
|
+
|
|
242
|
+
```yaml
|
|
243
|
+
system_info:
|
|
244
|
+
name: "Loja ACME"
|
|
245
|
+
stack:
|
|
246
|
+
backend: "Node.js (NestJS v11)"
|
|
247
|
+
frontend: "Next.js 16"
|
|
248
|
+
database: "PostgreSQL (Prisma 5)"
|
|
249
|
+
infra: "Docker / AWS ECS"
|
|
250
|
+
security:
|
|
251
|
+
auth_protocol: "JWT"
|
|
252
|
+
rbac_roles: ["ADMIN", "CUSTOMER"]
|
|
253
|
+
database_schemas:
|
|
254
|
+
- table: "orders"
|
|
255
|
+
columns: "id, customer_id, status, total, created_at"
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
git add .github/config/tech_context.yml
|
|
260
|
+
git commit -m "chore: tech_context.yml"
|
|
261
|
+
git push
|
|
262
|
+
|
|
263
|
+
# Acionar geração do plano
|
|
264
|
+
gh issue edit 12 --add-label "spec-wave:plan"
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
O Action gera `docs/features/checkout-com-pix/plan.md` com:
|
|
268
|
+
- Estratégia Técnica e Matriz de Rastreabilidade
|
|
269
|
+
- Detalhamento da Implementação
|
|
270
|
+
- Segurança e Conformidade
|
|
271
|
+
- Estratégia de Testes
|
|
272
|
+
- Rollback e Monitoramento
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
### 5. Validar spec e plan
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
gh issue edit 12 --add-label "spec-wave:ready"
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
O Action `validate.yml` verifica se todas as seções obrigatórias estão presentes em spec.md e plan.md. Se passar:
|
|
283
|
+
- Remove a label `spec-wave:ready`
|
|
284
|
+
- Adiciona a label `spec-wave:plan-approved`
|
|
285
|
+
- Comenta "Validação aprovada ✅" na issue
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
### 6. Decompor em Stories e Tasks
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
gh issue edit 12 --add-label "spec-wave:decompose"
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
O Action `decompose.yml` usa IA para criar sub-issues da Feature #12:
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
#13 [STORY] Como cliente, quero selecionar PIX como forma de pagamento
|
|
299
|
+
#14 [TASK] Criar endpoint POST /orders/:id/payment/pix
|
|
300
|
+
#15 [TASK] Integrar API do banco via webhook
|
|
301
|
+
#16 [TASK] Exibir QR Code na tela de checkout
|
|
302
|
+
#17 [STORY] Como cliente, quero receber confirmação do pagamento
|
|
303
|
+
#18 [TASK] Webhook de confirmação do banco
|
|
304
|
+
#19 [TASK] Notificação por e-mail ao confirmar
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Todas as Stories e Tasks são adicionadas ao board em **📋 Backlog Técnico** com Status `Todo`.
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
### 7. Implementar
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
# Via skill no Claude Code:
|
|
315
|
+
/spec-wave implement 13
|
|
316
|
+
|
|
317
|
+
# Ou direto:
|
|
318
|
+
npx @spec-wave/cli implement 13 --dry-run # ver contexto antes
|
|
319
|
+
npx @spec-wave/cli implement 13 # executar
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
O comando monta um arquivo de contexto com spec.md, plan.md e todas as Tasks da Story, e aciona o spec-kit configurado.
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
### 8. Code Review automático
|
|
327
|
+
|
|
328
|
+
Ao abrir um PR que referencia `Closes #14` (ou qualquer issue da hierarquia):
|
|
329
|
+
|
|
330
|
+
```markdown
|
|
331
|
+
## Descrição
|
|
332
|
+
Implementa endpoint PIX
|
|
333
|
+
|
|
334
|
+
Closes #14
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
O Action `code-review.yml` detecta a referência, sobe a hierarquia Task → Story → Feature, e move a Feature #12 para **👀 Code Review** no board.
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
### 9. QA automático
|
|
342
|
+
|
|
343
|
+
Quando um reviewer aprova o PR, o Action `qa.yml` move a Feature #12 para **🧪 QA**.
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
### 10. Estado final no board
|
|
348
|
+
|
|
349
|
+
```
|
|
350
|
+
Feature #12: [FEATURE] Checkout com PIX
|
|
351
|
+
Etapa: 🧪 QA
|
|
352
|
+
Status: Todo
|
|
353
|
+
Work Item Type: Feature
|
|
354
|
+
Priority: P1
|
|
355
|
+
Area: Backend
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Após QA passar, mover manualmente para **📋 Homologação → 🚀 Deploy → 🎉 Done**.
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Atualizar repositórios existentes
|
|
363
|
+
|
|
364
|
+
Quando uma nova versão da CLI for publicada, rode em cada repositório configurado:
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
npx @spec-wave/cli@latest init --skip-project --skip-labels
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Isso atualiza apenas os arquivos de workflow sem recriar o Project ou as labels.
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Providers de IA
|
|
375
|
+
|
|
376
|
+
| Provider | Secret | Modelo padrão |
|
|
377
|
+
|----------|--------|---------------|
|
|
378
|
+
| Anthropic | `ANTHROPIC_API_KEY` | `claude-sonnet-4-6` |
|
|
379
|
+
| OpenRouter | `OPENROUTER_API_KEY` | `anthropic/claude-3.7-sonnet` |
|
|
380
|
+
|
|
381
|
+
Configurar no `init`:
|
|
382
|
+
```bash
|
|
383
|
+
npx @spec-wave/cli init --repo owner/repo --provider openrouter --model anthropic/claude-3.7-sonnet
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Licença
|
|
389
|
+
|
|
390
|
+
MIT
|
package/bin/spec-wave.mjs
CHANGED
|
@@ -86,6 +86,20 @@ program
|
|
|
86
86
|
await feature(options).catch(err => { console.error(err.message); process.exit(1); });
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
+
program
|
|
90
|
+
.command('install-skill')
|
|
91
|
+
.description('Instala a skill spec-wave no(s) agente(s) detectado(s): Claude Code, Cursor, opencode, Cline, Kilo, Antigravity, AGENTS.md')
|
|
92
|
+
.option('--agent <names>', 'Agente(s) alvo, separados por vírgula (pula a detecção)')
|
|
93
|
+
.option('--all', 'Instala em todos os agentes detectados')
|
|
94
|
+
.option('--global', 'Instala no escopo do usuário (padrão: projeto)')
|
|
95
|
+
.option('--dry-run', 'Mostra o que seria instalado sem gravar')
|
|
96
|
+
.option('--force', 'Sobrescreve arquivos existentes sem confirmar')
|
|
97
|
+
.option('--yes', 'Modo não-interativo')
|
|
98
|
+
.action(async (options) => {
|
|
99
|
+
const { installSkill } = await import('../src/commands/install-skill.mjs');
|
|
100
|
+
await installSkill(options).catch(err => { console.error(err.message); process.exit(1); });
|
|
101
|
+
});
|
|
102
|
+
|
|
89
103
|
program
|
|
90
104
|
.command('uninstall')
|
|
91
105
|
.description('Remove labels, arquivos .github e o .spec-wave.json (mantém o GitHub Project)')
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
|
4
4
|
import { execSync } from 'node:child_process';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { resolveToken } from '../api/auth.mjs';
|
|
7
|
-
import { CONFIG_FILE } from '../config.mjs';
|
|
7
|
+
import { CONFIG_FILE, STAGE_IN_PROGRESS, STAGE_DONE, STAGE_CODE_REVIEW } from '../config.mjs';
|
|
8
8
|
import { getIssue } from '../api/github-rest.mjs';
|
|
9
9
|
import { listSubIssues, getIssueParent } from '../api/github-graphql.mjs';
|
|
10
10
|
import { detectIssueType } from '../lib/issue-type.mjs';
|
|
@@ -14,14 +14,17 @@ import { slugify } from '../lib/slugify.mjs';
|
|
|
14
14
|
const WORK_DIR = '.spec-wave';
|
|
15
15
|
|
|
16
16
|
// Sobe a cadeia de pais (Task → Story → Feature) até achar uma issue do tipo
|
|
17
|
-
// "Feature" e devolve
|
|
18
|
-
//
|
|
19
|
-
|
|
17
|
+
// "Feature" e devolve { number, title } — usado para resolver docs/features/<slug>
|
|
18
|
+
// e para as instruções de fim de Story (mover a Feature para Code Review). Limita
|
|
19
|
+
// a profundidade para evitar loops em dados inconsistentes.
|
|
20
|
+
async function resolveFeature(token, startNodeId) {
|
|
20
21
|
let current = startNodeId;
|
|
21
22
|
for (let depth = 0; depth < 5 && current; depth++) {
|
|
22
23
|
const parent = await getIssueParent(token, current);
|
|
23
24
|
if (!parent) return null;
|
|
24
|
-
if (detectIssueType({ title: parent.title }) === 'Feature')
|
|
25
|
+
if (detectIssueType({ title: parent.title }) === 'Feature') {
|
|
26
|
+
return { number: parent.number, title: parent.title };
|
|
27
|
+
}
|
|
25
28
|
current = parent.nodeId;
|
|
26
29
|
}
|
|
27
30
|
return null;
|
|
@@ -40,7 +43,7 @@ function readSpecPlan(featureDir) {
|
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
// Monta o markdown de contexto que será entregue ao spec-kit implement.
|
|
43
|
-
function buildContext({ type, issue, tasks, spec, plan, specPath, planPath }) {
|
|
46
|
+
function buildContext({ type, issue, tasks, feature, spec, plan, specPath, planPath }) {
|
|
44
47
|
const lines = [];
|
|
45
48
|
lines.push(`# Contexto de implementação — ${type} #${issue.number}`);
|
|
46
49
|
lines.push('');
|
|
@@ -50,32 +53,55 @@ function buildContext({ type, issue, tasks, spec, plan, specPath, planPath }) {
|
|
|
50
53
|
lines.push(issue.body.trim());
|
|
51
54
|
}
|
|
52
55
|
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
.filter((n, i, arr) => arr.indexOf(n) === i);
|
|
56
|
+
// Fluxo SEQUENCIAL, uma task por vez: cada task só vai para "In Progress"
|
|
57
|
+
// quando seu desenvolvimento começa e vai para "Done" ao concluir — nunca
|
|
58
|
+
// todas as tasks em "In Progress" ao mesmo tempo.
|
|
57
59
|
lines.push('');
|
|
58
|
-
lines.push('## Instruções
|
|
60
|
+
lines.push('## Instruções de execução (uma task por vez, sequencial)');
|
|
61
|
+
lines.push('');
|
|
62
|
+
if (type === 'Story') {
|
|
63
|
+
lines.push(
|
|
64
|
+
`Implemente as ${tasks.length} task(s) desta Story **uma de cada vez, na ordem listada ` +
|
|
65
|
+
'abaixo**. É PROIBIDO mover mais de uma task para "In Progress" ao mesmo tempo: uma task ' +
|
|
66
|
+
'só entra em desenvolvimento depois que a anterior estiver concluída.'
|
|
67
|
+
);
|
|
68
|
+
lines.push('');
|
|
69
|
+
lines.push(`1. Ao **iniciar a primeira** task, mova a Story #${issue.number} para **${STAGE_IN_PROGRESS}**.`);
|
|
70
|
+
lines.push('2. Para **cada task**, na ordem, execute este ciclo completo antes de passar para a próxima:');
|
|
71
|
+
lines.push(` 1. **Ao começar a task:** mova o status (campo "Etapa") *apenas dessa task* para **${STAGE_IN_PROGRESS}** (In Progress). Nenhuma outra task.`);
|
|
72
|
+
lines.push(' 2. **Implemente** essa task por completo.');
|
|
73
|
+
lines.push(` 3. **Ao concluir a task:** mova o status *dessa task* para **${STAGE_DONE}** (Done).`);
|
|
74
|
+
lines.push(' 4. Só então avance para a próxima task e repita o ciclo.');
|
|
75
|
+
lines.push('');
|
|
76
|
+
lines.push(`3. **Ao concluir a implementação de TODA a Story** (todas as tasks em **${STAGE_DONE}**):`);
|
|
77
|
+
lines.push(' 1. Faça o **commit** de todas as mudanças da implementação.');
|
|
78
|
+
lines.push(` 2. Abra o **Pull Request** da Story #${issue.number}.`);
|
|
79
|
+
lines.push(
|
|
80
|
+
` 3. Mova ${feature ? `a Feature #${feature.number}` : 'a Feature (issue pai da Story)'} e a ` +
|
|
81
|
+
`Story #${issue.number} para **${STAGE_CODE_REVIEW}**. ` +
|
|
82
|
+
`As **Tasks permanecem em ${STAGE_DONE}** (não as mova para trás).`
|
|
83
|
+
);
|
|
84
|
+
} else {
|
|
85
|
+
lines.push(`Implemente a Task #${issue.number} bracketando o status no GitHub Project:`);
|
|
86
|
+
lines.push('');
|
|
87
|
+
lines.push(`1. **Ao começar:** mova o status (campo "Etapa") da Task #${issue.number} para **${STAGE_IN_PROGRESS}** (In Progress).`);
|
|
88
|
+
lines.push('2. **Implemente** a task por completo.');
|
|
89
|
+
lines.push(`3. **Ao concluir:** mova o status da Task #${issue.number} para **${STAGE_DONE}** (Done).`);
|
|
90
|
+
}
|
|
59
91
|
lines.push('');
|
|
60
92
|
lines.push(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
? `da Story #${issue.number} e de cada Task: ${inProgress.filter(n => n !== issue.number).map(n => `#${n}`).join(', ')}.`
|
|
65
|
-
: `da Task #${issue.number}.`)
|
|
66
|
-
);
|
|
67
|
-
lines.push(
|
|
68
|
-
'(Atualize o campo "Etapa"/Status do item no board; mantenha o status coerente ' +
|
|
69
|
-
'conforme o progresso da implementação.)'
|
|
93
|
+
`> Mantenha o status coerente com o progresso real: nenhuma task pode ficar em "${STAGE_IN_PROGRESS}" ` +
|
|
94
|
+
`antes de você começá-la, nem em "${STAGE_DONE}" antes de concluí-la. Atualize o campo "Etapa"/Status ` +
|
|
95
|
+
'do item no GitHub Project.'
|
|
70
96
|
);
|
|
71
97
|
|
|
72
98
|
lines.push('');
|
|
73
|
-
lines.push(`## Tasks a implementar (${tasks.length})`);
|
|
74
|
-
|
|
99
|
+
lines.push(`## Tasks a implementar — NESTA ORDEM (${tasks.length})`);
|
|
100
|
+
tasks.forEach((t, i) => {
|
|
75
101
|
lines.push('');
|
|
76
|
-
lines.push(`### #${t.number} ${t.title}`);
|
|
102
|
+
lines.push(`### ${i + 1}. #${t.number} ${t.title}`);
|
|
77
103
|
if (t.body && t.body.trim()) lines.push(t.body.trim());
|
|
78
|
-
}
|
|
104
|
+
});
|
|
79
105
|
|
|
80
106
|
if (spec) {
|
|
81
107
|
lines.push('');
|
|
@@ -171,11 +197,12 @@ export async function implement({ issue: issueArg, featureDir: featureDirOpt, dr
|
|
|
171
197
|
return;
|
|
172
198
|
}
|
|
173
199
|
|
|
174
|
-
// 4. Resolve spec.md/plan.md
|
|
200
|
+
// 4. Resolve a Feature (pai na cadeia) — para spec.md/plan.md e para as
|
|
201
|
+
// instruções de fim de Story (mover Feature + Story para Code Review).
|
|
202
|
+
const feature = await resolveFeature(token, issue.node_id);
|
|
175
203
|
let featureDir = featureDirOpt;
|
|
176
|
-
if (!featureDir) {
|
|
177
|
-
|
|
178
|
-
if (featureTitle) featureDir = path.join('docs', 'features', slugify(featureTitle));
|
|
204
|
+
if (!featureDir && feature?.title) {
|
|
205
|
+
featureDir = path.join('docs', 'features', slugify(feature.title));
|
|
179
206
|
}
|
|
180
207
|
let specPlan = { spec: null, plan: null, specPath: null, planPath: null };
|
|
181
208
|
if (featureDir && existsSync(featureDir)) {
|
|
@@ -187,7 +214,7 @@ export async function implement({ issue: issueArg, featureDir: featureDirOpt, dr
|
|
|
187
214
|
}
|
|
188
215
|
|
|
189
216
|
// 5. Monta e grava o arquivo de contexto.
|
|
190
|
-
const context = buildContext({ type, issue, tasks, ...specPlan });
|
|
217
|
+
const context = buildContext({ type, issue, tasks, feature, ...specPlan });
|
|
191
218
|
mkdirSync(WORK_DIR, { recursive: true });
|
|
192
219
|
const tasksFile = path.join(WORK_DIR, `implement-${issueNumber}.md`);
|
|
193
220
|
writeFileSync(tasksFile, context);
|
package/src/commands/info.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import * as p from '@clack/prompts';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { readFileSync, existsSync } from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { CONFIG_FILE } from '../config.mjs';
|
|
5
|
+
import { CONFIG_FILE, PORTAL_URL } from '../config.mjs';
|
|
6
6
|
|
|
7
7
|
// Lê o marcador .spec-wave.json do repositório atual (cwd) e reporta se o
|
|
8
8
|
// spec-wave já foi inicializado. Usado pela skill para decidir entre mostrar
|
|
@@ -17,6 +17,7 @@ export async function info(options = {}) {
|
|
|
17
17
|
}
|
|
18
18
|
p.intro(chalk.bold('spec-wave info'));
|
|
19
19
|
p.log.warn(`Este repositório ${chalk.bold('não foi inicializado')} (sem ${CONFIG_FILE}).`);
|
|
20
|
+
p.log.info(`🌐 Acesse o Portal Web da ferramenta em ${chalk.cyan(PORTAL_URL)}`);
|
|
20
21
|
p.outro('Execute `npx @spec-wave/cli init` para configurar.');
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
@@ -50,5 +51,6 @@ export async function info(options = {}) {
|
|
|
50
51
|
`${chalk.dim('Criado em:')} ${config.initializedAt ?? '?'}`,
|
|
51
52
|
'Configuração'
|
|
52
53
|
);
|
|
54
|
+
p.log.info(`🌐 Acesse o Portal Web da ferramenta em ${chalk.cyan(PORTAL_URL)}`);
|
|
53
55
|
p.outro('Use `/spec-wave feature <descrição>` para criar uma Feature.');
|
|
54
56
|
}
|
package/src/commands/init.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { resolveToken, verifyTokenScopes } from '../api/auth.mjs';
|
|
@@ -8,8 +8,8 @@ import { runWizard } from '../ui/wizard.mjs';
|
|
|
8
8
|
import { setupProject } from '../setup/project.mjs';
|
|
9
9
|
import { setupLabels } from '../setup/labels.mjs';
|
|
10
10
|
import { setupFiles } from '../setup/files.mjs';
|
|
11
|
-
import {
|
|
12
|
-
import { CONFIG_FILE, AI_PROVIDERS, getProvider, DEFAULT_PROVIDER } from '../config.mjs';
|
|
11
|
+
import { getFileContent } from '../api/github-rest.mjs';
|
|
12
|
+
import { CONFIG_FILE, AI_PROVIDERS, getProvider, DEFAULT_PROVIDER, PORTAL_URL } from '../config.mjs';
|
|
13
13
|
|
|
14
14
|
const __dir = path.dirname(fileURLToPath(import.meta.url));
|
|
15
15
|
const pkg = JSON.parse(readFileSync(path.join(__dir, '..', '..', 'package.json'), 'utf-8'));
|
|
@@ -91,8 +91,13 @@ export async function init(options) {
|
|
|
91
91
|
if (options.skipProject) {
|
|
92
92
|
p.log.info('Pulando criação do GitHub Project (--skip-project).');
|
|
93
93
|
// Preserva o bloco project do .spec-wave.json existente (se houver).
|
|
94
|
+
// Prefere o arquivo local; recorre ao remoto para repos configurados por
|
|
95
|
+
// versões antigas (que commitavam o config direto no repo).
|
|
94
96
|
try {
|
|
95
|
-
const
|
|
97
|
+
const localConfigPath = path.join(process.cwd(), CONFIG_FILE);
|
|
98
|
+
const raw = existsSync(localConfigPath)
|
|
99
|
+
? readFileSync(localConfigPath, 'utf-8')
|
|
100
|
+
: await getFileContent(token, owner, repo, CONFIG_FILE);
|
|
96
101
|
if (raw) {
|
|
97
102
|
const existing = JSON.parse(raw);
|
|
98
103
|
if (existing.project) {
|
|
@@ -165,10 +170,13 @@ export async function init(options) {
|
|
|
165
170
|
}
|
|
166
171
|
|
|
167
172
|
// --- Marcador de configuração (.spec-wave.json) ---
|
|
168
|
-
//
|
|
169
|
-
//
|
|
173
|
+
// Gravado LOCALMENTE no diretório atual (não commitado direto no repo): é a
|
|
174
|
+
// fonte de estado persistente lida por info/refresh/uninstall/skill a partir
|
|
175
|
+
// do cwd. O usuário revisa e commita quando quiser.
|
|
176
|
+
const localConfigPath = path.join(process.cwd(), CONFIG_FILE);
|
|
177
|
+
let configWritten = false;
|
|
170
178
|
const configSpinner = p.spinner();
|
|
171
|
-
configSpinner.start(`Gravando ${CONFIG_FILE}...`);
|
|
179
|
+
configSpinner.start(`Gravando ${CONFIG_FILE} local...`);
|
|
172
180
|
try {
|
|
173
181
|
const config = {
|
|
174
182
|
version: pkg.version,
|
|
@@ -187,15 +195,9 @@ export async function init(options) {
|
|
|
187
195
|
},
|
|
188
196
|
initializedAt: new Date().toISOString(),
|
|
189
197
|
};
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
repo,
|
|
194
|
-
CONFIG_FILE,
|
|
195
|
-
JSON.stringify(config, null, 2) + '\n',
|
|
196
|
-
'chore: record spec-wave config [spec-wave]'
|
|
197
|
-
);
|
|
198
|
-
configSpinner.stop(`${CONFIG_FILE} gravado (spec-wave v${pkg.version})`);
|
|
198
|
+
writeFileSync(localConfigPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
199
|
+
configWritten = true;
|
|
200
|
+
configSpinner.stop(`${CONFIG_FILE} gravado local (spec-wave v${pkg.version})`);
|
|
199
201
|
} catch (err) {
|
|
200
202
|
configSpinner.stop('');
|
|
201
203
|
p.log.warn(`Não foi possível gravar ${CONFIG_FILE}: ${err.message}`);
|
|
@@ -214,10 +216,14 @@ export async function init(options) {
|
|
|
214
216
|
`\n${chalk.green('✓')} spec-wave configurado com sucesso!\n\n` +
|
|
215
217
|
(projectUrl ? ` Projeto: ${chalk.cyan(projectUrl)}\n\n` : '') +
|
|
216
218
|
` Próximos passos:\n` +
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
`
|
|
221
|
-
` ${
|
|
219
|
+
(configWritten
|
|
220
|
+
? ` 1. Commite o ${CONFIG_FILE} quando quiser (git add ${CONFIG_FILE} && git commit)\n`
|
|
221
|
+
: '') +
|
|
222
|
+
` ${configWritten ? '2' : '1'}. Adicione ${providerMeta.secret} como secret no repositório (provider: ${providerMeta.label})\n` +
|
|
223
|
+
` ${configWritten ? '3' : '2'}. Configure o board view para agrupar por "Etapa"\n` +
|
|
224
|
+
` ${configWritten ? '4' : '3'}. Crie uma Feature com o prefixo [FEATURE] no título\n` +
|
|
225
|
+
` ${configWritten ? '5' : '4'}. Use a skill spec-wave para guiar o fluxo\n\n` +
|
|
226
|
+
` ${chalk.dim('Para instalar a skill no seu agente: npx @spec-wave/cli install-skill')}\n\n` +
|
|
227
|
+
` 🌐 Acesse o Portal Web da ferramenta em ${chalk.cyan(PORTAL_URL)}`
|
|
222
228
|
);
|
|
223
229
|
}
|