@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spec-wave/cli",
3
- "version": "0.5.7",
3
+ "version": "0.5.9",
4
4
  "description": "Setup spec-driven GitHub workflow with Projects v2, labels, issue templates, and AI-powered Actions",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 seu título (para resolver docs/features/<slug>). Limita a
18
- // profundidade para evitar loops em dados inconsistentes.
19
- async function resolveFeatureTitle(token, startNodeId) {
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') return parent.title;
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
- // Issues cujo status deve ir para "in progress" ao iniciar: a issue alvo
54
- // (Story ou Task) e, no caso de Story, todas as suas Tasks.
55
- const inProgress = [issue.number, ...tasks.map(t => t.number)]
56
- .filter((n, i, arr) => arr.indexOf(n) === i);
56
+ // Fluxo SEQUENCIAL, uma task por vez: cada task 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 para o agente');
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
- 'Antes de começar a implementação, atualize o status no GitHub Project para ' +
62
- '**🚧 Desenvolvimento** (in progress) ' +
63
- (type === 'Story'
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
- for (const t of tasks) {
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 da Feature (enriquecimento opcional).
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
- const featureTitle = await resolveFeatureTitle(token, issue.node_id);
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);
@@ -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
  }
@@ -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 { upsertFile, getFileContent } from '../api/github-rest.mjs';
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 raw = await getFileContent(token, owner, repo, CONFIG_FILE);
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
- // Commitado no repo-alvo para que a skill detecte, em sessões futuras, que o
169
- // init rodou e qual project/versão foi usado. É a fonte de estado persistente.
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
- await upsertFile(
191
- token,
192
- owner,
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
- ` 1. Adicione ${providerMeta.secret} como secret no repositório (provider: ${providerMeta.label})\n` +
218
- ` 2. Configure o board view para agrupar por "Etapa"\n` +
219
- ` 3. Crie uma Feature com o prefixo [FEATURE] no título\n` +
220
- ` 4. Use a skill spec-wave para guiar o fluxo\n\n` +
221
- ` ${chalk.dim('Para usar a skill: adicione skill/SKILL.md ao seu projeto Claude Code')}`
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
  }