@spec-wave/cli 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -27
- package/bin/spec-wave.mjs +14 -4
- package/package.json +1 -1
- package/src/api/github-graphql.mjs +0 -4
- package/src/api/github-rest.mjs +0 -13
- package/src/commands/code-review.mjs +5 -8
- package/src/commands/decompose.mjs +410 -251
- package/src/commands/dev-agent.mjs +3 -2
- package/src/commands/doctor.mjs +239 -9
- package/src/commands/generate-plan.mjs +111 -51
- package/src/commands/generate-spec.mjs +20 -22
- package/src/commands/implement.mjs +46 -24
- package/src/commands/info.mjs +4 -3
- package/src/commands/issue.mjs +4 -4
- package/src/commands/move.mjs +162 -0
- package/src/commands/order.mjs +1 -12
- package/src/commands/qa.mjs +5 -8
- package/src/commands/refresh.mjs +4 -3
- package/src/commands/story.mjs +1 -12
- package/src/commands/task.mjs +1 -11
- package/src/commands/update.mjs +43 -19
- package/src/commands/validate.mjs +47 -35
- package/src/config.mjs +40 -6
- package/src/lib/board.mjs +88 -26
- package/src/lib/claude.mjs +315 -70
- package/src/lib/critique.mjs +391 -91
- package/src/lib/decomposition-doc.mjs +451 -0
- package/src/lib/implement-board.mjs +14 -1
- package/src/lib/project-root.mjs +93 -0
- package/src/lib/templates.mjs +53 -0
- package/src/setup/files.mjs +3 -10
- package/src/templates/skill/SKILL.md +137 -61
- package/src/templates/workflows/code-review.yml +1 -1
- package/src/templates/workflows/decompose.yml +20 -6
- package/src/templates/workflows/generate-plan.yml +1 -1
- package/src/templates/workflows/generate-spec.yml +1 -1
- package/src/templates/workflows/qa.yml +1 -1
- package/src/templates/workflows/validate.yml +1 -1
- package/src/lib/feature-docs.mjs +0 -89
- package/src/lib/force.mjs +0 -34
package/README.md
CHANGED
|
@@ -68,7 +68,8 @@ Ferramenta Node.js que configura e opera o fluxo via linha de comando.
|
|
|
68
68
|
| `generate-spec` | Gera `spec.md` (usado pelo GitHub Action) |
|
|
69
69
|
| `generate-plan` | Gera `plan.md` (usado pelo GitHub Action) |
|
|
70
70
|
| `validate` | Valida spec.md e plan.md (usado pelo GitHub Action) |
|
|
71
|
-
| `decompose` |
|
|
71
|
+
| `decompose` | Gera o rascunho da decomposição em `decomposition.md`; com `--apply`, cria as Stories/Tasks a partir do rascunho revisado (usado pelo GitHub Action) |
|
|
72
|
+
| `move <n> <etapa>` | Move qualquer item do board (Feature, Story, Task, Bug, RFC) para uma Etapa — a Etapa nunca retrocede |
|
|
72
73
|
| `code-review` | Move Feature para Code Review ao abrir PR (usado pelo GitHub Action) |
|
|
73
74
|
| `qa` | Move Feature para QA ao aprovar PR (usado pelo GitHub Action) |
|
|
74
75
|
| `implement` | Aciona o spec-kit localmente para implementar uma Feature (Stories pendentes em ordem de dependência), Story ou Task |
|
|
@@ -81,31 +82,10 @@ Ferramenta Node.js que configura e opera o fluxo via linha de comando.
|
|
|
81
82
|
| `generate-spec.yml` | label `spec-wave:spec` | Gera `docs/features/<slug>/spec.md` via IA |
|
|
82
83
|
| `generate-plan.yml` | label `spec-wave:plan` | Gera `docs/features/<slug>/plan.md` via IA |
|
|
83
84
|
| `validate.yml` | label `spec-wave:ready` | Valida seções obrigatórias; adiciona `spec-wave:plan-approved` |
|
|
84
|
-
| `decompose.yml` |
|
|
85
|
+
| `decompose.yml` | labels `spec-wave:decompose` / `spec-wave:decompose-apply` | 1º grava e critica o rascunho `decomposition.md`; 2º cria Stories e Tasks como sub-issues |
|
|
85
86
|
| `code-review.yml` | PR aberto/reaberto | Move Feature para `👀 Code Review` |
|
|
86
87
|
| `qa.yml` | PR aprovado | Move Feature para `🧪 QA` |
|
|
87
88
|
|
|
88
|
-
**Re-executar uma etapa (`spec-wave:force`).** Adicionada *junto* de uma label de gatilho, a label `spec-wave:force` manda o comando re-executar a etapa ignorando os guards; ela é consumida pelo run (vale uma vez). Sozinha não dispara nada.
|
|
89
|
-
|
|
90
|
-
```bash
|
|
91
|
-
gh issue edit 12 --add-label "spec-wave:force" --add-label "spec-wave:decompose"
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
No `decompose` ela **fecha as sub-issues da decomposição anterior** (Stories e suas Tasks) antes de gerar as novas — operação destrutiva, com aviso no comentário da issue quando alguma já saiu de `✅ Ready`. Em `generate-spec`/`generate-plan` ela **versiona** o documento em vez de sobrescrever (veja abaixo). A flag equivalente para execução local é `--force`.
|
|
95
|
-
|
|
96
|
-
### Documentos versionados
|
|
97
|
-
|
|
98
|
-
A primeira geração escreve `spec.md`/`plan.md` (v1). Cada regeração **forçada** grava a próxima versão ao lado, preservando as anteriores:
|
|
99
|
-
|
|
100
|
-
```
|
|
101
|
-
docs/features/<slug>/
|
|
102
|
-
spec.md ← v1
|
|
103
|
-
spec-v2.md ← regeração forçada ⬅ spec atual
|
|
104
|
-
plan.md ⬅ plano atual (spec e plan versionam independente)
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
A **maior versão é o documento atual**: é ela que o `generate-plan` usa como contexto, que o `validate` valida, e que o `decompose` e o `implement` leem. Uma regeração **sem** force sobrescreve essa versão atual.
|
|
108
|
-
|
|
109
89
|
### Skill (`src/templates/skill/SKILL.md`)
|
|
110
90
|
|
|
111
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).
|
|
@@ -306,20 +286,52 @@ O Action `validate.yml` verifica se todas as seções obrigatórias estão prese
|
|
|
306
286
|
|
|
307
287
|
---
|
|
308
288
|
|
|
309
|
-
### 6. Decompor em Stories e Tasks
|
|
289
|
+
### 6. Decompor em Stories e Tasks (duas etapas)
|
|
290
|
+
|
|
291
|
+
A decomposição não cria issues de uma vez. Primeiro nasce um **rascunho revisável**;
|
|
292
|
+
as issues só são criadas depois que você aprova.
|
|
293
|
+
|
|
294
|
+
**6a. Gerar o rascunho:**
|
|
310
295
|
|
|
311
296
|
```bash
|
|
312
297
|
gh issue edit 12 --add-label "spec-wave:decompose"
|
|
313
298
|
```
|
|
314
299
|
|
|
315
|
-
O Action `
|
|
300
|
+
O Action grava `docs/features/<slug>/decomposition.md` no repositório e submete o
|
|
301
|
+
arquivo à crítica adversarial. **Nenhuma issue é criada.**
|
|
302
|
+
|
|
303
|
+
```markdown
|
|
304
|
+
# Decomposição — [FEATURE] Pagamento com PIX
|
|
305
|
+
<!-- spec-wave:decomposition v1 issue=12 kind=stories -->
|
|
306
|
+
|
|
307
|
+
## Story 1 — selecionar PIX como forma de pagamento
|
|
308
|
+
|
|
309
|
+
**User story:** Como cliente, quero selecionar PIX, para pagar mais rápido
|
|
310
|
+
**Depende de:** —
|
|
311
|
+
|
|
312
|
+
### Task 1.1 — criar endpoint POST /orders/:id/payment/pix
|
|
313
|
+
### Task 1.2 — integrar API do banco via webhook
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Se a crítica encontrar contradições **graves**, ela comenta na issue citando
|
|
317
|
+
`Story N` / `Task N.M`, aplica `spec-wave:critique-failed` e o Action **falha**.
|
|
318
|
+
Você corrige o `decomposition.md` — o artefato que os achados referenciam — e
|
|
319
|
+
reaplica `spec-wave:decompose`: o arquivo é criticado como está, sem ser regerado.
|
|
320
|
+
|
|
321
|
+
**6b. Aplicar o rascunho aprovado:**
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
gh issue edit 12 --add-label "spec-wave:decompose-apply"
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Agora as sub-issues da Feature #12 são criadas a partir do arquivo:
|
|
316
328
|
|
|
317
329
|
```
|
|
318
|
-
#13 [STORY]
|
|
330
|
+
#13 [STORY] selecionar PIX como forma de pagamento
|
|
319
331
|
#14 [TASK] Criar endpoint POST /orders/:id/payment/pix
|
|
320
332
|
#15 [TASK] Integrar API do banco via webhook
|
|
321
333
|
#16 [TASK] Exibir QR Code na tela de checkout
|
|
322
|
-
#17 [STORY]
|
|
334
|
+
#17 [STORY] receber confirmação do pagamento
|
|
323
335
|
#18 [TASK] Webhook de confirmação do banco
|
|
324
336
|
#19 [TASK] Notificação por e-mail ao confirmar
|
|
325
337
|
```
|
package/bin/spec-wave.mjs
CHANGED
|
@@ -132,7 +132,6 @@ program
|
|
|
132
132
|
.command('generate-plan')
|
|
133
133
|
.description('Gera plan.md para uma Feature (usado pelo GitHub Action)')
|
|
134
134
|
.requiredOption('--issue-number <n>', 'Número da issue no GitHub')
|
|
135
|
-
.option('--force', 'Re-executa a etapa ignorando os guards (equivale à label spec-wave:force)')
|
|
136
135
|
.action(async (options) => {
|
|
137
136
|
const { generatePlan } = await import('../src/commands/generate-plan.mjs');
|
|
138
137
|
await generatePlan(options).catch(err => { console.error(err.message); process.exit(1); });
|
|
@@ -142,7 +141,6 @@ program
|
|
|
142
141
|
.command('generate-spec')
|
|
143
142
|
.description('Gera spec.md para uma Feature (usado pelo GitHub Action)')
|
|
144
143
|
.requiredOption('--issue-number <n>', 'Número da issue no GitHub')
|
|
145
|
-
.option('--force', 'Re-executa a etapa ignorando os guards (equivale à label spec-wave:force)')
|
|
146
144
|
.action(async (options) => {
|
|
147
145
|
const { generateSpec } = await import('../src/commands/generate-spec.mjs');
|
|
148
146
|
await generateSpec(options).catch(err => { console.error(err.message); process.exit(1); });
|
|
@@ -159,9 +157,9 @@ program
|
|
|
159
157
|
|
|
160
158
|
program
|
|
161
159
|
.command('decompose')
|
|
162
|
-
.description('
|
|
160
|
+
.description('Gera o rascunho da decomposição em decomposition.md; com --apply, cria as Stories/Tasks a partir do rascunho revisado (usado pelo GitHub Action)')
|
|
163
161
|
.requiredOption('--issue-number <n>', 'Número da issue no GitHub')
|
|
164
|
-
.option('--
|
|
162
|
+
.option('--apply', 'Aplica o decomposition.md já revisado: cria as issues (sem esta flag, apenas gera/critica o rascunho)')
|
|
165
163
|
.action(async (options) => {
|
|
166
164
|
const { decompose } = await import('../src/commands/decompose.mjs');
|
|
167
165
|
await decompose(options).catch(err => { console.error(err.message); process.exit(1); });
|
|
@@ -215,6 +213,18 @@ program
|
|
|
215
213
|
await task({ action, issue: n }).catch(err => { console.error(err.message); process.exit(1); });
|
|
216
214
|
});
|
|
217
215
|
|
|
216
|
+
program
|
|
217
|
+
.command('move')
|
|
218
|
+
.description('Move qualquer item do board (Feature, Story, Task, Bug, RFC) para uma Etapa — a Etapa nunca retrocede')
|
|
219
|
+
.argument('<n>', 'Número da issue, ex.: 8 ou #8')
|
|
220
|
+
.argument('<etapa>', 'Etapa de destino, com ou sem emoji, ex.: "code review", "Homologação", "🎉 Done"')
|
|
221
|
+
.option('--status <valor>', 'Valor do campo Status no destino: Todo, In Progress ou Done (default: Todo)')
|
|
222
|
+
.action(async (n, etapa, options) => {
|
|
223
|
+
const { move } = await import('../src/commands/move.mjs');
|
|
224
|
+
await move({ issue: n, stage: etapa, ...options })
|
|
225
|
+
.catch(err => { console.error(err.message); process.exit(1); });
|
|
226
|
+
});
|
|
227
|
+
|
|
218
228
|
program
|
|
219
229
|
.command('story')
|
|
220
230
|
.description('Gerencia uma Story no board: review (move para Code Review)')
|
package/package.json
CHANGED
|
@@ -198,7 +198,6 @@ export async function listSubIssues(token, issueNodeId) {
|
|
|
198
198
|
number
|
|
199
199
|
title
|
|
200
200
|
body
|
|
201
|
-
state
|
|
202
201
|
labels(first: 20) { nodes { name } }
|
|
203
202
|
}
|
|
204
203
|
}
|
|
@@ -212,9 +211,6 @@ export async function listSubIssues(token, issueNodeId) {
|
|
|
212
211
|
title: n.title,
|
|
213
212
|
body: n.body || '',
|
|
214
213
|
nodeId: n.id,
|
|
215
|
-
// 'OPEN' | 'CLOSED' (GraphQL) normalizado para o vocabulário do REST, que
|
|
216
|
-
// é o que o resto do código compara (issue.state === 'closed').
|
|
217
|
-
state: n.state === 'CLOSED' ? 'closed' : 'open',
|
|
218
214
|
labels: (n.labels?.nodes || []).map(l => l.name),
|
|
219
215
|
}));
|
|
220
216
|
}
|
package/src/api/github-rest.mjs
CHANGED
|
@@ -101,19 +101,6 @@ export async function getIssue(token, owner, repo, issueNumber) {
|
|
|
101
101
|
return res.data;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
// Fecha uma issue. Usado pelo re-decompose forçado para limpar as sub-issues
|
|
105
|
-
// da decomposição anterior antes de gerar as novas.
|
|
106
|
-
export async function closeIssue(token, owner, repo, issueNumber, reason = 'not_planned') {
|
|
107
|
-
const octokit = makeOctokit(token);
|
|
108
|
-
await octokit.rest.issues.update({
|
|
109
|
-
owner,
|
|
110
|
-
repo,
|
|
111
|
-
issue_number: issueNumber,
|
|
112
|
-
state: 'closed',
|
|
113
|
-
state_reason: reason,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
104
|
export async function deleteLabel(token, owner, repo, name) {
|
|
118
105
|
const octokit = makeOctokit(token);
|
|
119
106
|
try {
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
1
|
import { resolveToken } from '../api/auth.mjs';
|
|
4
2
|
import { getIssue, getPR, commentOnIssue } from '../api/github-rest.mjs';
|
|
5
3
|
import { addProjectItem, getIssueParent, listSubIssues, getItemSingleSelectValue } from '../api/github-graphql.mjs';
|
|
6
4
|
import { detectIssueType } from '../lib/issue-type.mjs';
|
|
7
5
|
import { loadProjectConfig, resolveField, advanceToStage } from '../lib/board.mjs';
|
|
8
|
-
import {
|
|
6
|
+
import { loadConfig } from '../lib/project-root.mjs';
|
|
7
|
+
import { STATUS_OPTIONS, STAGE_ORDER, STAGE_DONE, PROGRESS_TODO, PROGRESS_DONE, isManualStageType } from '../config.mjs';
|
|
9
8
|
|
|
10
9
|
// Ao abrir o PR: Stories/Feature → Etapa "👀 Code Review" (Status "Todo");
|
|
11
10
|
// Tasks → Etapa "🎉 Done" (Status "Done"), pois a implementação da task terminou.
|
|
@@ -118,11 +117,9 @@ export async function codeReview({ prNumber }) {
|
|
|
118
117
|
const token = await resolveToken();
|
|
119
118
|
const projectToken = process.env.PROJECT_TOKEN || token;
|
|
120
119
|
const [envOwner, envRepo] = (process.env.GITHUB_REPOSITORY || '').split('/');
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const owner = envOwner || cfg.owner;
|
|
125
|
-
const repo = envRepo || cfg.repo;
|
|
120
|
+
const { config: cfg } = loadConfig();
|
|
121
|
+
const owner = envOwner || cfg?.owner;
|
|
122
|
+
const repo = envRepo || cfg?.repo;
|
|
126
123
|
|
|
127
124
|
if (!owner || !repo) {
|
|
128
125
|
throw new Error(
|