@spec-wave/cli 0.8.0 → 0.9.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 +9 -9
- package/bin/spec-wave.mjs +2 -2
- package/package.json +2 -2
- package/src/commands/doctor.mjs +5 -5
- package/src/commands/implement.mjs +446 -69
- package/src/commands/info.mjs +3 -3
- package/src/commands/init.mjs +1 -1
- package/src/commands/install-skill.mjs +4 -4
- package/src/commands/update.mjs +1 -1
- package/src/lib/implement-board.mjs +104 -0
- package/src/templates/skill/SKILL.md +35 -31
- package/src/templates/workflows/code-review.yml +1 -1
- package/src/templates/workflows/decompose.yml +1 -1
- 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/README.md
CHANGED
|
@@ -72,7 +72,7 @@ Ferramenta Node.js que configura e opera o fluxo via linha de comando.
|
|
|
72
72
|
| `decompose` | Decompõe Feature em Stories e Tasks (usado pelo GitHub Action) |
|
|
73
73
|
| `code-review` | Move Feature para Code Review ao abrir PR (usado pelo GitHub Action) |
|
|
74
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 |
|
|
75
|
+
| `implement` | Aciona o spec-kit localmente para implementar uma Feature (Stories pendentes em ordem de dependência), Story ou Task |
|
|
76
76
|
| `uninstall` | Remove labels, workflows e `.spec-wave.json` |
|
|
77
77
|
|
|
78
78
|
### GitHub Actions (instalados pelo `init`)
|
|
@@ -117,7 +117,7 @@ Arquivo em `.github/config/tech_context.yml` que descreve a stack tecnológica d
|
|
|
117
117
|
Não é necessário instalar globalmente — use `npx`:
|
|
118
118
|
|
|
119
119
|
```bash
|
|
120
|
-
npx @spec-wave/cli --help
|
|
120
|
+
npx @spec-wave/cli@latest --help
|
|
121
121
|
```
|
|
122
122
|
|
|
123
123
|
Para instalar globalmente:
|
|
@@ -137,7 +137,7 @@ A skill permite usar o fluxo diretamente no seu agente via `/spec-wave`.
|
|
|
137
137
|
|
|
138
138
|
```bash
|
|
139
139
|
# Autodetecta o agente em uso e instala no local/formato correto
|
|
140
|
-
npx @spec-wave/cli install-skill
|
|
140
|
+
npx @spec-wave/cli@latest install-skill
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
O comando suporta Claude Code, Cursor, opencode, Cline, Kilo Code, Antigravity e o
|
|
@@ -184,7 +184,7 @@ gh auth status
|
|
|
184
184
|
gh auth refresh --scopes project,repo,workflow
|
|
185
185
|
|
|
186
186
|
# Configurar spec-wave (cria Project, labels e workflows)
|
|
187
|
-
npx @spec-wave/cli init --repo acme/loja --project-title "Loja — Spec Wave"
|
|
187
|
+
npx @spec-wave/cli@latest init --repo acme/loja --project-title "Loja — Spec Wave"
|
|
188
188
|
```
|
|
189
189
|
|
|
190
190
|
O `init` cria:
|
|
@@ -201,7 +201,7 @@ Adicionar o secret de IA no GitHub: **Settings → Secrets → Actions → `ANTH
|
|
|
201
201
|
|
|
202
202
|
```bash
|
|
203
203
|
# Criar Epic
|
|
204
|
-
npx @spec-wave/cli issue \
|
|
204
|
+
npx @spec-wave/cli@latest issue \
|
|
205
205
|
--type epic \
|
|
206
206
|
--title "Checkout e Pagamentos" \
|
|
207
207
|
--priority P1 \
|
|
@@ -209,7 +209,7 @@ npx @spec-wave/cli issue \
|
|
|
209
209
|
# → Issue #5 criada: [EPIC] Checkout e Pagamentos
|
|
210
210
|
|
|
211
211
|
# Criar Feature como sub-issue do Epic
|
|
212
|
-
npx @spec-wave/cli feature \
|
|
212
|
+
npx @spec-wave/cli@latest feature \
|
|
213
213
|
--title "Checkout com PIX" \
|
|
214
214
|
--parent 5 \
|
|
215
215
|
--priority P1 \
|
|
@@ -315,8 +315,8 @@ Todas as Stories e Tasks são adicionadas ao board em **📋 Backlog Técnico**
|
|
|
315
315
|
/spec-wave implement 13
|
|
316
316
|
|
|
317
317
|
# Ou direto:
|
|
318
|
-
npx @spec-wave/cli implement 13 --dry-run # ver contexto antes
|
|
319
|
-
npx @spec-wave/cli implement 13 # executar
|
|
318
|
+
npx @spec-wave/cli@latest implement 13 --dry-run # ver contexto antes
|
|
319
|
+
npx @spec-wave/cli@latest implement 13 # executar
|
|
320
320
|
```
|
|
321
321
|
|
|
322
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.
|
|
@@ -380,7 +380,7 @@ Isso atualiza apenas os arquivos de workflow sem recriar o Project ou as labels.
|
|
|
380
380
|
|
|
381
381
|
Configurar no `init`:
|
|
382
382
|
```bash
|
|
383
|
-
npx @spec-wave/cli init --repo owner/repo --provider openrouter --model anthropic/claude-3.7-sonnet
|
|
383
|
+
npx @spec-wave/cli@latest init --repo owner/repo --provider openrouter --model anthropic/claude-3.7-sonnet
|
|
384
384
|
```
|
|
385
385
|
|
|
386
386
|
---
|
package/bin/spec-wave.mjs
CHANGED
|
@@ -184,8 +184,8 @@ program
|
|
|
184
184
|
|
|
185
185
|
program
|
|
186
186
|
.command('implement')
|
|
187
|
-
.description('Aciona o spec-kit implement para uma Story (todas as tasks) ou uma Task')
|
|
188
|
-
.argument('<issue>', 'Número da issue (Story ou Task), ex.: 12 ou #12')
|
|
187
|
+
.description('Aciona o spec-kit implement para uma Feature (Stories pendentes em ordem de dependência), uma Story (todas as tasks) ou uma Task')
|
|
188
|
+
.argument('<issue>', 'Número da issue (Feature, Story ou Task), ex.: 12 ou #12')
|
|
189
189
|
.option('--feature-dir <path>', 'Caminho do docs/features/<slug> (sobrescreve a resolução automática)')
|
|
190
190
|
.option('--dry-run', 'Monta o contexto e imprime o comando sem executar o spec-kit')
|
|
191
191
|
.action(async (issue, options) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spec-wave/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
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": {
|
|
@@ -28,4 +28,4 @@
|
|
|
28
28
|
"commander": "^13.1.0",
|
|
29
29
|
"js-yaml": "^4.1.0"
|
|
30
30
|
}
|
|
31
|
-
}
|
|
31
|
+
}
|
package/src/commands/doctor.mjs
CHANGED
|
@@ -202,7 +202,7 @@ async function checkConfig(ctx) {
|
|
|
202
202
|
return {
|
|
203
203
|
name,
|
|
204
204
|
status: 'fail',
|
|
205
|
-
detail: `${CONFIG_FILE} não encontrado em ${ctx.cwd}. Rode \`npx @spec-wave/cli init\`.`,
|
|
205
|
+
detail: `${CONFIG_FILE} não encontrado em ${ctx.cwd}. Rode \`npx @spec-wave/cli@latest init\`.`,
|
|
206
206
|
};
|
|
207
207
|
}
|
|
208
208
|
if (ctx.cfgError) {
|
|
@@ -219,7 +219,7 @@ async function checkConfig(ctx) {
|
|
|
219
219
|
status: 'warn',
|
|
220
220
|
detail:
|
|
221
221
|
notes.join('\n') +
|
|
222
|
-
`\nproject.fields sem ${missingFields.map((f) => `"${f}"`).join(' e ')} — rode \`npx @spec-wave/cli refresh\`.`,
|
|
222
|
+
`\nproject.fields sem ${missingFields.map((f) => `"${f}"`).join(' e ')} — rode \`npx @spec-wave/cli@latest refresh\`.`,
|
|
223
223
|
};
|
|
224
224
|
}
|
|
225
225
|
|
|
@@ -240,7 +240,7 @@ async function checkConfig(ctx) {
|
|
|
240
240
|
status: 'warn',
|
|
241
241
|
detail:
|
|
242
242
|
notes.join('\n') +
|
|
243
|
-
`\nOpções de Etapa do config ausentes no project real: ${diverged.join(', ')} — rode \`npx @spec-wave/cli refresh\`.`,
|
|
243
|
+
`\nOpções de Etapa do config ausentes no project real: ${diverged.join(', ')} — rode \`npx @spec-wave/cli@latest refresh\`.`,
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
246
|
notes.push(`Project "${snapshot.title}" verificado — campos Etapa/Status em sincronia.`);
|
|
@@ -371,7 +371,7 @@ async function checkWorkflows(ctx) {
|
|
|
371
371
|
return {
|
|
372
372
|
name,
|
|
373
373
|
status: 'warn',
|
|
374
|
-
detail: '.github/workflows/ não encontrado — rode `npx @spec-wave/cli init` (ou `update`) para instalar os workflows.',
|
|
374
|
+
detail: '.github/workflows/ não encontrado — rode `npx @spec-wave/cli@latest init` (ou `update`) para instalar os workflows.',
|
|
375
375
|
};
|
|
376
376
|
}
|
|
377
377
|
const present = readdirSync(dir);
|
|
@@ -380,7 +380,7 @@ async function checkWorkflows(ctx) {
|
|
|
380
380
|
return {
|
|
381
381
|
name,
|
|
382
382
|
status: 'warn',
|
|
383
|
-
detail: `Workflows faltando em .github/workflows/: ${missing.join(', ')} — rode \`npx @spec-wave/cli update\`.`,
|
|
383
|
+
detail: `Workflows faltando em .github/workflows/: ${missing.join(', ')} — rode \`npx @spec-wave/cli@latest update\`.`,
|
|
384
384
|
};
|
|
385
385
|
}
|
|
386
386
|
return { name, status: 'ok', detail: `Os ${WORKFLOW_FILES.length} workflows do spec-wave estão presentes.` };
|
|
@@ -5,19 +5,25 @@ import { execSync } from 'node:child_process';
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { resolveToken } from '../api/auth.mjs';
|
|
7
7
|
import {
|
|
8
|
-
CONFIG_FILE, STAGE_DEVELOPMENT, STAGE_CODE_REVIEW, STAGE_DONE,
|
|
8
|
+
CONFIG_FILE, STAGE_DEVELOPMENT, STAGE_CODE_REVIEW, STAGE_DONE, STAGE_ORDER,
|
|
9
9
|
PROGRESS_TODO, PROGRESS_IN_PROGRESS, PROGRESS_DONE,
|
|
10
10
|
} from '../config.mjs';
|
|
11
11
|
import { getIssue, listIssueComments, listBlockedBy } from '../api/github-rest.mjs';
|
|
12
|
-
import { listSubIssues, getIssueParent } from '../api/github-graphql.mjs';
|
|
12
|
+
import { listSubIssues, getIssueParent, addProjectItem, getItemSingleSelectValue } from '../api/github-graphql.mjs';
|
|
13
13
|
import { detectIssueType } from '../lib/issue-type.mjs';
|
|
14
14
|
import { slugify } from '../lib/slugify.mjs';
|
|
15
|
-
import { parseDependencies } from '../lib/dependencies.mjs';
|
|
15
|
+
import { parseDependencies, orderStories, formatDependencyLine } from '../lib/dependencies.mjs';
|
|
16
|
+
import { loadProjectConfig, resolveField } from '../lib/board.mjs';
|
|
17
|
+
import { planBoardMoves, applyBoardMoves } from '../lib/implement-board.mjs';
|
|
16
18
|
import { extractPathsFromPlan, buildCodeDigest } from '../lib/code-digest.mjs';
|
|
17
19
|
|
|
18
20
|
// Diretório onde montamos o arquivo de contexto entregue ao spec-kit.
|
|
19
21
|
const WORK_DIR = '.spec-wave';
|
|
20
22
|
|
|
23
|
+
// Caps dos comentários anexados ao contexto (por issue).
|
|
24
|
+
const MAX_COMMENTS_PER_ISSUE = 15;
|
|
25
|
+
const MAX_COMMENT_CHARS = 2000;
|
|
26
|
+
|
|
21
27
|
// Sobe a cadeia de pais (Task → Story → Feature) até achar uma issue do tipo
|
|
22
28
|
// "Feature" e devolve { number, title } — usado para resolver docs/features/<slug>
|
|
23
29
|
// e para as instruções de fim de Story (mover a Feature para Code Review). Limita
|
|
@@ -47,6 +53,89 @@ function readSpecPlan(featureDir) {
|
|
|
47
53
|
};
|
|
48
54
|
}
|
|
49
55
|
|
|
56
|
+
// ── Blocos compartilhados entre buildContext (Story/Task) e buildFeatureContext ──
|
|
57
|
+
|
|
58
|
+
// Explica os dois campos do board (Etapa × Status) — abre as instruções de execução.
|
|
59
|
+
function boardFieldsExplainer() {
|
|
60
|
+
return (
|
|
61
|
+
'Há **dois campos** no board com papéis diferentes — não os confunda:\n' +
|
|
62
|
+
`- **Etapa** (Backlog → … → ${STAGE_DEVELOPMENT} → ${STAGE_CODE_REVIEW} → … → ${STAGE_DONE}): a DIREÇÃO no kanban. Uma issue só **avança**, **nunca** volta para uma etapa anterior.\n` +
|
|
63
|
+
`- **Status** (${PROGRESS_TODO} → ${PROGRESS_IN_PROGRESS} → ${PROGRESS_DONE}): o **progresso dentro da etapa atual**. Ao avançar de etapa, o Status reinicia em ${PROGRESS_TODO} — exceto ao chegar na Etapa ${STAGE_DONE}, onde o Status fica **${PROGRESS_DONE}**.`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Blockquote-resumo da regra do board — fecha as instruções de execução.
|
|
68
|
+
function boardRuleBlockquote() {
|
|
69
|
+
return (
|
|
70
|
+
`> **Regra do board:** a **Etapa** só avança (nunca retrocede); o **Status** (${PROGRESS_TODO}/${PROGRESS_IN_PROGRESS}/${PROGRESS_DONE}) ` +
|
|
71
|
+
`mede o progresso dentro da etapa atual e reinicia a cada avanço (na Etapa ${STAGE_DONE}, o Status fica ${PROGRESS_DONE}).`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Dependências ainda abertas — logo após o cabeçalho, para máxima visibilidade.
|
|
76
|
+
function pushBlockedByWarnings(lines, blockedByWarnings) {
|
|
77
|
+
if (!blockedByWarnings || blockedByWarnings.length === 0) return;
|
|
78
|
+
lines.push('');
|
|
79
|
+
lines.push('## ⚠️ Dependências pendentes');
|
|
80
|
+
lines.push('');
|
|
81
|
+
for (const w of blockedByWarnings) lines.push(`- ${w}`);
|
|
82
|
+
lines.push('');
|
|
83
|
+
lines.push(
|
|
84
|
+
'**Implemente somente se tiver certeza de que a dependência não é bloqueante; ' +
|
|
85
|
+
'caso contrário, pare e reporte.**'
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Comentários das issues — é onde vivem as revisões/correções feitas depois
|
|
90
|
+
// que spec/plan/stories foram escritos; em conflito, o comentário vence.
|
|
91
|
+
function pushCommentsSection(lines, comments) {
|
|
92
|
+
if (!comments || comments.length === 0) return;
|
|
93
|
+
lines.push('');
|
|
94
|
+
lines.push('## Comentários das issues (revisões e correções)');
|
|
95
|
+
lines.push('');
|
|
96
|
+
lines.push(
|
|
97
|
+
'> Comentários frequentemente **corrigem ou substituem** instruções dos documentos ' +
|
|
98
|
+
'acima — em caso de conflito, o comentário mais recente prevalece.'
|
|
99
|
+
);
|
|
100
|
+
for (const group of comments) {
|
|
101
|
+
lines.push('');
|
|
102
|
+
lines.push(`### Comentários da ${group.kind} #${group.issueNumber}`);
|
|
103
|
+
if (group.total > group.items.length) {
|
|
104
|
+
lines.push('');
|
|
105
|
+
lines.push(`_(mostrando os ${group.items.length} mais recentes de ${group.total})_`);
|
|
106
|
+
}
|
|
107
|
+
for (const c of group.items) {
|
|
108
|
+
lines.push('');
|
|
109
|
+
lines.push(`**${c.author || 'desconhecido'}** (${c.createdAt}):`);
|
|
110
|
+
lines.push('');
|
|
111
|
+
lines.push(c.body.trim());
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Estado atual do código + spec.md + plan.md — fecho comum dos dois contextos.
|
|
117
|
+
function pushDigestSpecPlan(lines, { codeDigest, spec, plan, specPath, planPath }) {
|
|
118
|
+
if (codeDigest) {
|
|
119
|
+
lines.push('');
|
|
120
|
+
lines.push('## Estado atual do código');
|
|
121
|
+
lines.push('');
|
|
122
|
+
lines.push('> **NÃO reimplemente o que já existe; estenda os módulos listados abaixo.**');
|
|
123
|
+
lines.push('');
|
|
124
|
+
lines.push(codeDigest.trim());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (spec) {
|
|
128
|
+
lines.push('');
|
|
129
|
+
lines.push(`## spec.md (${specPath})`);
|
|
130
|
+
lines.push(spec.trim());
|
|
131
|
+
}
|
|
132
|
+
if (plan) {
|
|
133
|
+
lines.push('');
|
|
134
|
+
lines.push(`## plan.md (${planPath})`);
|
|
135
|
+
lines.push(plan.trim());
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
50
139
|
// Monta o markdown de contexto que será entregue ao spec-kit implement.
|
|
51
140
|
function buildContext({
|
|
52
141
|
type, issue, tasks, feature, siblingStories = [], spec, plan, specPath, planPath,
|
|
@@ -61,18 +150,7 @@ function buildContext({
|
|
|
61
150
|
lines.push(issue.body.trim());
|
|
62
151
|
}
|
|
63
152
|
|
|
64
|
-
|
|
65
|
-
if (blockedByWarnings.length > 0) {
|
|
66
|
-
lines.push('');
|
|
67
|
-
lines.push('## ⚠️ Dependências pendentes');
|
|
68
|
-
lines.push('');
|
|
69
|
-
for (const w of blockedByWarnings) lines.push(`- ${w}`);
|
|
70
|
-
lines.push('');
|
|
71
|
-
lines.push(
|
|
72
|
-
'**Implemente somente se tiver certeza de que a dependência não é bloqueante; ' +
|
|
73
|
-
'caso contrário, pare e reporte.**'
|
|
74
|
-
);
|
|
75
|
-
}
|
|
153
|
+
pushBlockedByWarnings(lines, blockedByWarnings);
|
|
76
154
|
|
|
77
155
|
// Modelo do board: "Etapa" (coluna do kanban) = DIREÇÃO, só avança; "Status"
|
|
78
156
|
// (Todo/In Progress/Done) = PROGRESSO dentro da etapa. O desenvolvimento de
|
|
@@ -81,10 +159,13 @@ function buildContext({
|
|
|
81
159
|
lines.push('');
|
|
82
160
|
lines.push('## Instruções de execução (uma task por vez, sequencial)');
|
|
83
161
|
lines.push('');
|
|
162
|
+
lines.push(boardFieldsExplainer());
|
|
163
|
+
lines.push('');
|
|
84
164
|
lines.push(
|
|
85
|
-
'
|
|
86
|
-
|
|
87
|
-
|
|
165
|
+
'> **O board é atualizado automaticamente pelo spec-wave**: no início, ' +
|
|
166
|
+
'Feature/Story vão para Desenvolvimento (In Progress); na conclusão, ' +
|
|
167
|
+
'Tasks vão para Done e a Story para Code Review. As instruções de board ' +
|
|
168
|
+
'abaixo descrevem o modelo — você NÃO precisa mover cards manualmente.'
|
|
88
169
|
);
|
|
89
170
|
lines.push('');
|
|
90
171
|
if (type === 'Story') {
|
|
@@ -125,10 +206,7 @@ function buildContext({
|
|
|
125
206
|
lines.push(`3. **Ao concluir:** **avance a Task #${issue.number} para a Etapa ${STAGE_DONE}** com Status **${PROGRESS_DONE}**.`);
|
|
126
207
|
}
|
|
127
208
|
lines.push('');
|
|
128
|
-
lines.push(
|
|
129
|
-
`> **Regra do board:** a **Etapa** só avança (nunca retrocede); o **Status** (${PROGRESS_TODO}/${PROGRESS_IN_PROGRESS}/${PROGRESS_DONE}) ` +
|
|
130
|
-
`mede o progresso dentro da etapa atual e reinicia a cada avanço (na Etapa ${STAGE_DONE}, o Status fica ${PROGRESS_DONE}).`
|
|
131
|
-
);
|
|
209
|
+
lines.push(boardRuleBlockquote());
|
|
132
210
|
|
|
133
211
|
lines.push('');
|
|
134
212
|
lines.push(`## Tasks a implementar — NESTA ORDEM (${tasks.length})`);
|
|
@@ -138,52 +216,136 @@ function buildContext({
|
|
|
138
216
|
if (t.body && t.body.trim()) lines.push(t.body.trim());
|
|
139
217
|
});
|
|
140
218
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
219
|
+
pushCommentsSection(lines, comments);
|
|
220
|
+
pushDigestSpecPlan(lines, { codeDigest, spec, plan, specPath, planPath });
|
|
221
|
+
|
|
222
|
+
lines.push('');
|
|
223
|
+
return lines.join('\n');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Planeja a implementação de uma Feature: separa as Stories já implementadas
|
|
228
|
+
* (Etapa >= reviewStage na ordem canônica) das pendentes e ordena as pendentes
|
|
229
|
+
* topologicamente pelas dependências. Pura — sem I/O. NUNCA lança.
|
|
230
|
+
*
|
|
231
|
+
* Stories com stage null/desconhecido contam como pendentes (mais seguro
|
|
232
|
+
* incluir do que pular em silêncio). O grafo é montado só com as pendentes:
|
|
233
|
+
* dependências para Stories puladas (ou externas) contam como satisfeitas, e o
|
|
234
|
+
* `cycle` retornado só acusa ciclos entre pendentes.
|
|
235
|
+
*
|
|
236
|
+
* @param {Array<{number:number, stage:string|null, dependsOn?:number[]}>} stories
|
|
237
|
+
* @param {{reviewStage?:string, stageOrder?:string[]}} [opts]
|
|
238
|
+
* @returns {{ pending: object[], skipped: object[], cycle: number[] }}
|
|
239
|
+
* pending: objetos originais na ordem de execução; skipped: em ordem
|
|
240
|
+
* crescente de number; cycle: numbers pendentes em/bloqueados por ciclo.
|
|
241
|
+
*/
|
|
242
|
+
export function planFeatureImplementation(stories, {
|
|
243
|
+
reviewStage = STAGE_CODE_REVIEW,
|
|
244
|
+
stageOrder = STAGE_ORDER,
|
|
245
|
+
} = {}) {
|
|
246
|
+
const list = Array.isArray(stories) ? stories : [];
|
|
247
|
+
const reviewIdx = stageOrder.indexOf(reviewStage);
|
|
248
|
+
const skipped = [];
|
|
249
|
+
const pendingSet = [];
|
|
250
|
+
for (const s of list) {
|
|
251
|
+
const idx = s.stage ? stageOrder.indexOf(s.stage) : -1;
|
|
252
|
+
if (reviewIdx !== -1 && idx !== -1 && idx >= reviewIdx) skipped.push(s);
|
|
253
|
+
else pendingSet.push(s);
|
|
254
|
+
}
|
|
255
|
+
skipped.sort((a, b) => a.number - b.number);
|
|
256
|
+
|
|
257
|
+
const { order, cycle } = orderStories(
|
|
258
|
+
pendingSet.map(s => ({ number: s.number, dependsOn: s.dependsOn || [] }))
|
|
259
|
+
);
|
|
260
|
+
const byNumber = new Map(pendingSet.map(s => [s.number, s]));
|
|
261
|
+
const pending = order.map(n => byNumber.get(n)).filter(Boolean);
|
|
262
|
+
return { pending, skipped, cycle };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Monta o markdown de contexto do modo Feature (puro — testável): todas as
|
|
267
|
+
* Stories pendentes em ordem de execução, cada uma com suas Tasks, mais as já
|
|
268
|
+
* implementadas (não tocar), comentários, digest e spec/plan.
|
|
269
|
+
*/
|
|
270
|
+
export function buildFeatureContext({
|
|
271
|
+
feature, stories, skipped = [],
|
|
272
|
+
spec, plan, specPath, planPath,
|
|
273
|
+
comments = [], codeDigest = null, blockedByWarnings = [],
|
|
274
|
+
}) {
|
|
275
|
+
const lines = [];
|
|
276
|
+
lines.push(`# Contexto de implementação — Feature #${feature.number}`);
|
|
277
|
+
lines.push('');
|
|
278
|
+
lines.push(`**Feature:** ${feature.title}`);
|
|
279
|
+
if (feature.body && feature.body.trim()) {
|
|
146
280
|
lines.push('');
|
|
147
|
-
lines.push(
|
|
148
|
-
'> Comentários frequentemente **corrigem ou substituem** instruções dos documentos ' +
|
|
149
|
-
'acima — em caso de conflito, o comentário mais recente prevalece.'
|
|
150
|
-
);
|
|
151
|
-
for (const group of comments) {
|
|
152
|
-
lines.push('');
|
|
153
|
-
lines.push(`### Comentários da ${group.kind} #${group.issueNumber}`);
|
|
154
|
-
if (group.total > group.items.length) {
|
|
155
|
-
lines.push('');
|
|
156
|
-
lines.push(`_(mostrando os ${group.items.length} mais recentes de ${group.total})_`);
|
|
157
|
-
}
|
|
158
|
-
for (const c of group.items) {
|
|
159
|
-
lines.push('');
|
|
160
|
-
lines.push(`**${c.author || 'desconhecido'}** (${c.createdAt}):`);
|
|
161
|
-
lines.push('');
|
|
162
|
-
lines.push(c.body.trim());
|
|
163
|
-
}
|
|
164
|
-
}
|
|
281
|
+
lines.push(feature.body.trim());
|
|
165
282
|
}
|
|
166
283
|
|
|
167
|
-
|
|
168
|
-
|
|
284
|
+
pushBlockedByWarnings(lines, blockedByWarnings);
|
|
285
|
+
|
|
286
|
+
lines.push('');
|
|
287
|
+
lines.push('## Instruções de execução (uma Story por vez, na ordem)');
|
|
288
|
+
lines.push('');
|
|
289
|
+
lines.push(boardFieldsExplainer());
|
|
290
|
+
lines.push('');
|
|
291
|
+
lines.push(
|
|
292
|
+
`Implemente as ${stories.length} story(ies) pendentes abaixo **uma de cada vez, na ordem listada** — ` +
|
|
293
|
+
'a ordem já respeita as dependências entre elas (linhas `Depende de:`). Para **cada Story**, na ordem:'
|
|
294
|
+
);
|
|
295
|
+
lines.push('');
|
|
296
|
+
lines.push(`1. **Ao começar a Story:** garanta que ela está na Etapa **${STAGE_DEVELOPMENT}** com Status **${PROGRESS_IN_PROGRESS}** (as Tasks dela nessa Etapa com Status **${PROGRESS_TODO}**).`);
|
|
297
|
+
lines.push(`2. Implemente as Tasks da Story **uma de cada vez, na ordem listada**. É PROIBIDO ter mais de uma Task com Status **${PROGRESS_IN_PROGRESS}** ao mesmo tempo. Para **cada Task**:`);
|
|
298
|
+
lines.push(` 1. **Ao começar:** Status da Task → **${PROGRESS_IN_PROGRESS}** (a Etapa continua ${STAGE_DEVELOPMENT}).`);
|
|
299
|
+
lines.push(' 2. **Implemente** a Task por completo.');
|
|
300
|
+
lines.push(` 3. **Ao concluir:** **avance a Task para a Etapa ${STAGE_DONE}** com Status **${PROGRESS_DONE}**.`);
|
|
301
|
+
lines.push(`3. **Ao concluir TODAS as Tasks da Story:** faça o **commit**, abra o **Pull Request** da Story e **avance a Etapa da Story para ${STAGE_CODE_REVIEW}** (Status ${PROGRESS_TODO}).`);
|
|
302
|
+
lines.push('4. Só então inicie a próxima Story.');
|
|
303
|
+
lines.push('');
|
|
304
|
+
lines.push(
|
|
305
|
+
`**Feature #${feature.number}:** avance-a para a Etapa **${STAGE_CODE_REVIEW}** (Status ${PROGRESS_TODO}) ` +
|
|
306
|
+
`**somente após concluir a ÚLTIMA Story da lista**${skipped.length > 0 ? ' (as Stories já implementadas listadas abaixo não precisam ser refeitas)' : ''}. ` +
|
|
307
|
+
`Enquanto houver Story pendente, a Feature permanece em ${STAGE_DEVELOPMENT}.`
|
|
308
|
+
);
|
|
309
|
+
lines.push('');
|
|
310
|
+
lines.push(boardRuleBlockquote());
|
|
311
|
+
|
|
312
|
+
if (skipped.length > 0) {
|
|
169
313
|
lines.push('');
|
|
170
|
-
lines.push(
|
|
314
|
+
lines.push(`## Stories já implementadas — NÃO tocar (${skipped.length})`);
|
|
171
315
|
lines.push('');
|
|
172
|
-
lines.push(
|
|
316
|
+
lines.push(`> Já estão em ${STAGE_CODE_REVIEW} ou além; **não** as reimplemente nem as mova.`);
|
|
173
317
|
lines.push('');
|
|
174
|
-
|
|
318
|
+
for (const s of skipped) {
|
|
319
|
+
lines.push(`- #${s.number} ${s.title} — Etapa atual: ${s.stage || '—'}`);
|
|
320
|
+
}
|
|
175
321
|
}
|
|
176
322
|
|
|
177
|
-
|
|
323
|
+
lines.push('');
|
|
324
|
+
lines.push(`## Stories a implementar — NESTA ORDEM (${stories.length})`);
|
|
325
|
+
stories.forEach((s, i) => {
|
|
178
326
|
lines.push('');
|
|
179
|
-
lines.push(
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
327
|
+
lines.push(`### ${i + 1}. Story #${s.number} — ${s.title}`);
|
|
328
|
+
const depLine = formatDependencyLine(s.dependsOn || []);
|
|
329
|
+
if (depLine) {
|
|
330
|
+
lines.push('');
|
|
331
|
+
lines.push(depLine);
|
|
332
|
+
}
|
|
333
|
+
if (s.body && s.body.trim()) {
|
|
334
|
+
lines.push('');
|
|
335
|
+
lines.push(s.body.trim());
|
|
336
|
+
}
|
|
337
|
+
const tasks = s.tasks || [];
|
|
183
338
|
lines.push('');
|
|
184
|
-
lines.push(
|
|
185
|
-
|
|
186
|
-
|
|
339
|
+
lines.push(`#### Tasks da Story #${s.number} — NESTA ORDEM (${tasks.length})`);
|
|
340
|
+
tasks.forEach((t, j) => {
|
|
341
|
+
lines.push('');
|
|
342
|
+
lines.push(`##### ${j + 1}. #${t.number} ${t.title}`);
|
|
343
|
+
if (t.body && t.body.trim()) lines.push(t.body.trim());
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
pushCommentsSection(lines, comments);
|
|
348
|
+
pushDigestSpecPlan(lines, { codeDigest, spec, plan, specPath, planPath });
|
|
187
349
|
|
|
188
350
|
lines.push('');
|
|
189
351
|
return lines.join('\n');
|
|
@@ -194,6 +356,197 @@ function renderCommand(template, vars) {
|
|
|
194
356
|
return template.replace(/\{(\w+)\}/g, (m, key) => (key in vars ? vars[key] : m));
|
|
195
357
|
}
|
|
196
358
|
|
|
359
|
+
// Modo Feature: avalia as Stories da Feature (dependências + Etapa no board),
|
|
360
|
+
// pula as já implementadas (Code Review+) e monta UM contexto único com todas
|
|
361
|
+
// as pendentes em ordem topológica — spec-kit acionado uma vez.
|
|
362
|
+
async function implementFeature({ token, owner, repo, config, feature, featureDirOpt, dryRun }) {
|
|
363
|
+
// F1. Stories (sub-issues) da Feature.
|
|
364
|
+
const subs = await listSubIssues(token, feature.node_id).catch(() => []);
|
|
365
|
+
const stories = subs.filter(s => detectIssueType({ title: s.title, labels: s.labels }) === 'Story');
|
|
366
|
+
if (stories.length === 0) {
|
|
367
|
+
p.log.error(
|
|
368
|
+
`Feature #${feature.number} não tem Stories (sub-issues). ` +
|
|
369
|
+
'Decomponha primeiro: adicione a label spec-wave:decompose.'
|
|
370
|
+
);
|
|
371
|
+
process.exitCode = 1;
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
p.log.info(`Feature com ${stories.length} story(ies): ${stories.map(s => `#${s.number}`).join(', ')}`);
|
|
375
|
+
|
|
376
|
+
// F1-board. Feature → Desenvolvimento (In Progress) já no início.
|
|
377
|
+
await applyBoardMoves({ token, moves: planBoardMoves('start', {
|
|
378
|
+
feature: { nodeId: feature.node_id, number: feature.number },
|
|
379
|
+
}) });
|
|
380
|
+
|
|
381
|
+
// F2. Dependências de cada Story: linha "Depende de:" do body ∪ blocked_by nativo.
|
|
382
|
+
const enriched = await Promise.all(stories.map(async (s) => {
|
|
383
|
+
let body = s.body;
|
|
384
|
+
if (!body) body = (await getIssue(token, owner, repo, s.number).catch(() => null))?.body || '';
|
|
385
|
+
const deps = new Set(parseDependencies(body));
|
|
386
|
+
const blocked = await listBlockedBy(token, owner, repo, s.number).catch(() => []);
|
|
387
|
+
for (const b of blocked) deps.add(b.number);
|
|
388
|
+
return { number: s.number, title: s.title, nodeId: s.nodeId, body: body || '', dependsOn: [...deps] };
|
|
389
|
+
}));
|
|
390
|
+
|
|
391
|
+
// F3. Etapa de cada Story no board (best-effort — sem board, nada é pulado).
|
|
392
|
+
const { project, error: projectError } = loadProjectConfig();
|
|
393
|
+
const stageOf = new Map();
|
|
394
|
+
if (projectError) {
|
|
395
|
+
p.log.warn(`${projectError} — Etapas do board não consultadas; nenhuma Story será considerada implementada.`);
|
|
396
|
+
} else {
|
|
397
|
+
const etapaField = await resolveField(token, project, 'Etapa').catch(() => null);
|
|
398
|
+
if (etapaField?.id) {
|
|
399
|
+
await Promise.all(enriched.map(async (s) => {
|
|
400
|
+
try {
|
|
401
|
+
const itemId = await addProjectItem(token, project.id, s.nodeId);
|
|
402
|
+
stageOf.set(s.number, await getItemSingleSelectValue(token, itemId, etapaField.id));
|
|
403
|
+
} catch {
|
|
404
|
+
stageOf.set(s.number, null);
|
|
405
|
+
}
|
|
406
|
+
}));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// F4. Planejamento: pendentes em ordem topológica, puladas, ciclos.
|
|
411
|
+
const { pending, skipped, cycle } = planFeatureImplementation(
|
|
412
|
+
enriched.map(s => ({ ...s, stage: stageOf.get(s.number) ?? null }))
|
|
413
|
+
);
|
|
414
|
+
if (cycle.length > 0) {
|
|
415
|
+
p.log.error(
|
|
416
|
+
`Ciclo de dependências entre Stories pendentes: ${cycle.map(n => `#${n}`).join(', ')}. ` +
|
|
417
|
+
'Corrija as linhas "Depende de:" (ou as relações blocked by) dessas Stories — ' +
|
|
418
|
+
`use \`spec-wave order ${feature.number}\` para visualizar.`
|
|
419
|
+
);
|
|
420
|
+
process.exitCode = 1;
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (skipped.length > 0) {
|
|
424
|
+
p.log.info(
|
|
425
|
+
`Puladas (já em ${STAGE_CODE_REVIEW}+): ` +
|
|
426
|
+
skipped.map(s => `#${s.number} (${s.stage})`).join(', ')
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
if (pending.length === 0) {
|
|
430
|
+
p.outro(`Todas as ${stories.length} story(ies) da Feature #${feature.number} já estão implementadas — nada a fazer.`);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
p.log.info(`Ordem de implementação: ${pending.map(s => `#${s.number}`).join(' → ')}`);
|
|
434
|
+
|
|
435
|
+
// F5. Tasks de cada Story pendente.
|
|
436
|
+
const noTasks = [];
|
|
437
|
+
for (const s of pending) {
|
|
438
|
+
const storySubs = await listSubIssues(token, s.nodeId).catch(() => []);
|
|
439
|
+
s.tasks = storySubs
|
|
440
|
+
.filter(t => detectIssueType({ title: t.title, labels: t.labels }) === 'Task')
|
|
441
|
+
.map(t => ({ number: t.number, title: t.title, body: t.body || '', nodeId: t.nodeId }));
|
|
442
|
+
if (s.tasks.length === 0) noTasks.push(s.number);
|
|
443
|
+
}
|
|
444
|
+
if (noTasks.length > 0) {
|
|
445
|
+
p.log.error(
|
|
446
|
+
`Story(ies) pendente(s) sem Tasks (sub-issues): ${noTasks.map(n => `#${n}`).join(', ')}. ` +
|
|
447
|
+
'Decomponha-as antes de implementar (re-rode o decompose da Feature se necessário).'
|
|
448
|
+
);
|
|
449
|
+
process.exitCode = 1;
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// F6. spec.md/plan.md — a issue-alvo JÁ é a Feature (sem resolveFeature).
|
|
454
|
+
const featureDir = featureDirOpt || path.join('docs', 'features', slugify(feature.title));
|
|
455
|
+
let specPlan = { spec: null, plan: null, specPath: null, planPath: null };
|
|
456
|
+
if (existsSync(featureDir)) {
|
|
457
|
+
specPlan = readSpecPlan(featureDir);
|
|
458
|
+
} else {
|
|
459
|
+
p.log.warn(`Diretório da feature não encontrado (${featureDir}); seguindo só com as Stories.`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// F7. Dependências EXTERNAS ainda abertas (da Feature e das Stories pendentes)
|
|
463
|
+
// — as internas ao conjunto de Stories já estão cobertas pela ordem topológica.
|
|
464
|
+
const blockedByWarnings = [];
|
|
465
|
+
try {
|
|
466
|
+
const internal = new Set(stories.map(s => s.number));
|
|
467
|
+
const featureDeps = new Set(parseDependencies(feature.body));
|
|
468
|
+
const featureBlocked = await listBlockedBy(token, owner, repo, feature.number).catch(() => []);
|
|
469
|
+
for (const b of featureBlocked) featureDeps.add(b.number);
|
|
470
|
+
const check = [
|
|
471
|
+
{ kind: 'Feature', number: feature.number, deps: featureDeps },
|
|
472
|
+
...pending.map(s => ({ kind: 'Story', number: s.number, deps: new Set(s.dependsOn) })),
|
|
473
|
+
];
|
|
474
|
+
for (const c of check) {
|
|
475
|
+
for (const depNumber of [...c.deps].sort((a, b) => a - b)) {
|
|
476
|
+
if (internal.has(depNumber)) continue;
|
|
477
|
+
const dep = await getIssue(token, owner, repo, depNumber).catch(() => null);
|
|
478
|
+
if (!dep || dep.state === 'closed') continue;
|
|
479
|
+
const warning =
|
|
480
|
+
`${c.kind} #${c.number} depende de #${depNumber} («${dep.title}»), ` +
|
|
481
|
+
`que ainda não está concluída (state: ${dep.state}).`;
|
|
482
|
+
blockedByWarnings.push(warning);
|
|
483
|
+
p.log.warn(warning);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
} catch { /* aviso é best-effort — segue sem ele */ }
|
|
487
|
+
|
|
488
|
+
// F8. Comentários: Feature primeiro, depois cada Story pendente na ordem.
|
|
489
|
+
const comments = [];
|
|
490
|
+
const commentSources = [
|
|
491
|
+
{ number: feature.number, kind: 'Feature' },
|
|
492
|
+
...pending.map(s => ({ number: s.number, kind: 'Story' })),
|
|
493
|
+
];
|
|
494
|
+
for (const src of commentSources) {
|
|
495
|
+
const all = await listIssueComments(token, owner, repo, src.number).catch(() => []);
|
|
496
|
+
if (all.length === 0) continue;
|
|
497
|
+
const items = all.slice(-MAX_COMMENTS_PER_ISSUE).map(c => ({
|
|
498
|
+
...c,
|
|
499
|
+
body: c.body.length > MAX_COMMENT_CHARS
|
|
500
|
+
? `${c.body.slice(0, MAX_COMMENT_CHARS)}…[truncado]`
|
|
501
|
+
: c.body,
|
|
502
|
+
}));
|
|
503
|
+
comments.push({ issueNumber: src.number, kind: src.kind, total: all.length, items });
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// F9. Digest do estado do código desde a criação da Feature.
|
|
507
|
+
let codeDigest = null;
|
|
508
|
+
try {
|
|
509
|
+
const paths = specPlan.plan ? extractPathsFromPlan(specPlan.plan) : [];
|
|
510
|
+
codeDigest = await buildCodeDigest({ sinceIso: feature.created_at || null, paths });
|
|
511
|
+
} catch {
|
|
512
|
+
codeDigest = null;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// F10. Contexto único + spec-kit (uma execução).
|
|
516
|
+
const context = buildFeatureContext({
|
|
517
|
+
feature: { number: feature.number, title: feature.title, body: feature.body || '' },
|
|
518
|
+
stories: pending,
|
|
519
|
+
skipped,
|
|
520
|
+
...specPlan,
|
|
521
|
+
comments,
|
|
522
|
+
codeDigest,
|
|
523
|
+
blockedByWarnings,
|
|
524
|
+
});
|
|
525
|
+
await writeContextAndRunSpecKit({
|
|
526
|
+
config,
|
|
527
|
+
issueNumber: feature.number,
|
|
528
|
+
type: 'Feature',
|
|
529
|
+
title: feature.title,
|
|
530
|
+
specPlan,
|
|
531
|
+
context,
|
|
532
|
+
dryRun,
|
|
533
|
+
outroSuccess:
|
|
534
|
+
`${chalk.green('✓')} Implementação acionada para a Feature #${feature.number} ` +
|
|
535
|
+
`(${pending.length} story(ies) pendente(s)).\n` +
|
|
536
|
+
` Próximo: acompanhe os PRs de cada Story; a Feature avança para ${STAGE_CODE_REVIEW} após a última.`,
|
|
537
|
+
onSuccess: async () => {
|
|
538
|
+
// Execução única para todas as pendentes: no sucesso, Tasks → Done e
|
|
539
|
+
// Stories → Code Review (a Feature fica com a Action de PR/code-review).
|
|
540
|
+
for (const s of pending) {
|
|
541
|
+
await applyBoardMoves({ token, moves: planBoardMoves('success', {
|
|
542
|
+
story: { nodeId: s.nodeId, number: s.number },
|
|
543
|
+
tasks: s.tasks || [],
|
|
544
|
+
}) });
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
|
|
197
550
|
export async function implement({ issue: issueArg, featureDir: featureDirOpt, dryRun }) {
|
|
198
551
|
const issueNumber = parseInt(String(issueArg).replace('#', ''), 10);
|
|
199
552
|
if (!Number.isInteger(issueNumber)) {
|
|
@@ -258,11 +611,15 @@ export async function implement({ issue: issueArg, featureDir: featureDirOpt, dr
|
|
|
258
611
|
}
|
|
259
612
|
p.log.info(`Story com ${tasks.length} task(s): ${tasks.map(t => `#${t.number}`).join(', ')}`);
|
|
260
613
|
} else if (type === 'Task') {
|
|
261
|
-
tasks = [{ number: issue.number, title: issue.title, body: issue.body || '' }];
|
|
614
|
+
tasks = [{ number: issue.number, title: issue.title, body: issue.body || '', nodeId: issue.node_id }];
|
|
262
615
|
p.log.info(`Task única #${issueNumber}.`);
|
|
616
|
+
} else if (type === 'Feature') {
|
|
617
|
+
// Modo Feature: Stories pendentes em ordem de dependência, contexto único.
|
|
618
|
+
await implementFeature({ token, owner, repo, config, feature: issue, featureDirOpt, dryRun });
|
|
619
|
+
return;
|
|
263
620
|
} else {
|
|
264
621
|
p.log.error(
|
|
265
|
-
`implement só aceita Story ou Task. Issue #${issueNumber} é do tipo ${type || 'desconhecido'}.`
|
|
622
|
+
`implement só aceita Feature, Story ou Task. Issue #${issueNumber} é do tipo ${type || 'desconhecido'}.`
|
|
266
623
|
);
|
|
267
624
|
process.exitCode = 1;
|
|
268
625
|
return;
|
|
@@ -284,6 +641,16 @@ export async function implement({ issue: issueArg, featureDir: featureDirOpt, dr
|
|
|
284
641
|
p.log.warn('Não foi possível resolver a Feature; seguindo só com as tasks (use --feature-dir).');
|
|
285
642
|
}
|
|
286
643
|
|
|
644
|
+
// 4a-board. Board determinístico: Feature/Story → Desenvolvimento (In
|
|
645
|
+
// Progress) já no início — a UI mostra o desenvolvimento em andamento sem
|
|
646
|
+
// depender de o LLM mover cards. Best-effort: falha vira warn.
|
|
647
|
+
await applyBoardMoves({ token, moves: planBoardMoves('start', {
|
|
648
|
+
feature: feature ? { nodeId: feature.nodeId, number: feature.number } : null,
|
|
649
|
+
story: type === 'Story' ? { nodeId: issue.node_id, number: issue.number } : null,
|
|
650
|
+
tasks: tasks.map(t => ({ nodeId: t.nodeId, number: t.number })),
|
|
651
|
+
tasksStartStatus: type === 'Task' ? PROGRESS_IN_PROGRESS : PROGRESS_TODO,
|
|
652
|
+
}) });
|
|
653
|
+
|
|
287
654
|
// 4b. Stories irmãs da Feature — a Feature só avança para Code Review quando
|
|
288
655
|
// TODAS as suas Stories estiverem implementadas. Lista as outras para o agente
|
|
289
656
|
// verificar antes de mover a Feature.
|
|
@@ -298,8 +665,6 @@ export async function implement({ issue: issueArg, featureDir: featureDirOpt, dr
|
|
|
298
665
|
// 4c. Comentários das issues (best-effort) — revisões e correções vivem nos
|
|
299
666
|
// comentários, não no body; sem eles o agente implementa instruções já
|
|
300
667
|
// corrigidas. Feature primeiro (correções de escopo), depois a issue-alvo.
|
|
301
|
-
const MAX_COMMENTS_PER_ISSUE = 15;
|
|
302
|
-
const MAX_COMMENT_CHARS = 2000;
|
|
303
668
|
const comments = [];
|
|
304
669
|
const commentSources = [];
|
|
305
670
|
if (feature && feature.number !== issue.number) {
|
|
@@ -353,17 +718,31 @@ export async function implement({ issue: issueArg, featureDir: featureDirOpt, dr
|
|
|
353
718
|
}
|
|
354
719
|
} catch { /* aviso é best-effort — segue sem ele */ }
|
|
355
720
|
|
|
356
|
-
// 5. Monta e
|
|
721
|
+
// 5-6. Monta o contexto e aciona o spec-kit (comando configurável).
|
|
357
722
|
const context = buildContext({
|
|
358
723
|
type, issue, tasks, feature, siblingStories, ...specPlan,
|
|
359
724
|
comments, codeDigest, blockedByWarnings,
|
|
360
725
|
});
|
|
726
|
+
await writeContextAndRunSpecKit({
|
|
727
|
+
config, issueNumber, type, title: issue.title, specPlan, context, dryRun,
|
|
728
|
+
outroSuccess:
|
|
729
|
+
`${chalk.green('✓')} Implementação acionada para ${type} #${issueNumber}.\n` +
|
|
730
|
+
' Próximo: revise as mudanças e abra o PR — o board já foi atualizado.',
|
|
731
|
+
onSuccess: () => applyBoardMoves({ token, moves: planBoardMoves('success', {
|
|
732
|
+
story: type === 'Story' ? { nodeId: issue.node_id, number: issue.number } : null,
|
|
733
|
+
tasks: tasks.map(t => ({ nodeId: t.nodeId, number: t.number })),
|
|
734
|
+
}) }),
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Grava o arquivo de contexto e aciona o spec-kit (comando configurável) —
|
|
739
|
+
// fecho comum dos modos Feature e Story/Task.
|
|
740
|
+
async function writeContextAndRunSpecKit({ config, issueNumber, type, title, specPlan, context, dryRun, outroSuccess, onSuccess }) {
|
|
361
741
|
mkdirSync(WORK_DIR, { recursive: true });
|
|
362
742
|
const tasksFile = path.join(WORK_DIR, `implement-${issueNumber}.md`);
|
|
363
743
|
writeFileSync(tasksFile, context);
|
|
364
744
|
p.log.success(`Contexto montado em ${chalk.cyan(tasksFile)}.`);
|
|
365
745
|
|
|
366
|
-
// 6. Aciona o spec-kit (comando configurável).
|
|
367
746
|
const template = process.env.SPEC_WAVE_IMPLEMENT_CMD || config.specKit?.command;
|
|
368
747
|
const vars = {
|
|
369
748
|
tasksFile,
|
|
@@ -371,7 +750,7 @@ export async function implement({ issue: issueArg, featureDir: featureDirOpt, dr
|
|
|
371
750
|
planFile: specPlan.planPath || '',
|
|
372
751
|
issue: String(issueNumber),
|
|
373
752
|
type,
|
|
374
|
-
title
|
|
753
|
+
title,
|
|
375
754
|
};
|
|
376
755
|
|
|
377
756
|
if (!template) {
|
|
@@ -404,8 +783,6 @@ export async function implement({ issue: issueArg, featureDir: featureDirOpt, dr
|
|
|
404
783
|
return;
|
|
405
784
|
}
|
|
406
785
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
' Próximo: revise as mudanças, abra o PR e mova o card para 👀 Code Review.'
|
|
410
|
-
);
|
|
786
|
+
if (onSuccess) await onSuccess();
|
|
787
|
+
p.outro(outroSuccess);
|
|
411
788
|
}
|
package/src/commands/info.mjs
CHANGED
|
@@ -23,7 +23,7 @@ function reportSkill(status) {
|
|
|
23
23
|
if (status.agentsDetected.length === 0) {
|
|
24
24
|
p.log.warn(
|
|
25
25
|
'Nenhum agente de código detectado neste diretório — a skill spec-wave ' +
|
|
26
|
-
'não parece instalada. Rode `npx @spec-wave/cli install-skill`.'
|
|
26
|
+
'não parece instalada. Rode `npx @spec-wave/cli@latest install-skill`.'
|
|
27
27
|
);
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
@@ -34,7 +34,7 @@ function reportSkill(status) {
|
|
|
34
34
|
p.log.warn(
|
|
35
35
|
'Skill pendente de instalação/atualização:\n' +
|
|
36
36
|
status.pending.map(s => ` ${chalk.yellow('↻')} ${s.agent} (${s.reason}) — ${chalk.dim(s.path)}`).join('\n') +
|
|
37
|
-
'\nRode `npx @spec-wave/cli install-skill` (ou `npx @spec-wave/cli update`).'
|
|
37
|
+
'\nRode `npx @spec-wave/cli@latest install-skill` (ou `npx @spec-wave/cli@latest update`).'
|
|
38
38
|
);
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -55,7 +55,7 @@ export async function info(options = {}) {
|
|
|
55
55
|
p.log.warn(`Este repositório ${chalk.bold('não foi inicializado')} (sem ${CONFIG_FILE}).`);
|
|
56
56
|
reportSkill(skill);
|
|
57
57
|
p.log.info(`🌐 Acesse o Portal Web da ferramenta em ${chalk.cyan(PORTAL_URL)}`);
|
|
58
|
-
p.outro('Execute `npx @spec-wave/cli init` para configurar.');
|
|
58
|
+
p.outro('Execute `npx @spec-wave/cli@latest init` para configurar.');
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
|
package/src/commands/init.mjs
CHANGED
|
@@ -223,7 +223,7 @@ export async function init(options) {
|
|
|
223
223
|
` ${configWritten ? '3' : '2'}. Configure o board view para agrupar por "Etapa"\n` +
|
|
224
224
|
` ${configWritten ? '4' : '3'}. Crie uma Feature com o prefixo [FEATURE] no título\n` +
|
|
225
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` +
|
|
226
|
+
` ${chalk.dim('Para instalar a skill no seu agente: npx @spec-wave/cli@latest install-skill')}\n\n` +
|
|
227
227
|
` 🌐 Acesse o Portal Web da ferramenta em ${chalk.cyan(PORTAL_URL)}`
|
|
228
228
|
);
|
|
229
229
|
}
|
|
@@ -100,13 +100,13 @@ export function parseSkill(raw) {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
// Banner de versão inserido no topo do corpo da skill instalada. O agente lê
|
|
103
|
-
// esta linha e, se `npx @spec-wave/cli --version` for maior, orienta reinstalar.
|
|
103
|
+
// esta linha e, se `npx @spec-wave/cli@latest --version` for maior, orienta reinstalar.
|
|
104
104
|
function versionBanner(version) {
|
|
105
105
|
return (
|
|
106
106
|
`> ⚙️ **spec-wave skill v${version}** — esta skill é uma cópia estática. ` +
|
|
107
|
-
'Se `npx @spec-wave/cli --version` indicar uma versão maior, ela está ' +
|
|
108
|
-
'desatualizada: rode `npx @spec-wave/cli update` (atualiza só o que mudou) ' +
|
|
109
|
-
'ou `npx @spec-wave/cli install-skill --force` (só a skill).'
|
|
107
|
+
'Se `npx @spec-wave/cli@latest --version` indicar uma versão maior, ela está ' +
|
|
108
|
+
'desatualizada: rode `npx @spec-wave/cli@latest update` (atualiza só o que mudou) ' +
|
|
109
|
+
'ou `npx @spec-wave/cli@latest install-skill --force` (só a skill).'
|
|
110
110
|
);
|
|
111
111
|
}
|
|
112
112
|
|
package/src/commands/update.mjs
CHANGED
|
@@ -210,7 +210,7 @@ export async function update(options = {}) {
|
|
|
210
210
|
// Config (.spec-wave.json) — reconsulta o Project e reescreve local.
|
|
211
211
|
if (configStale) {
|
|
212
212
|
if (!configStale.canApply) {
|
|
213
|
-
p.log.warn(`${CONFIG_FILE}: sem project.id — pulei. Rode \`npx @spec-wave/cli init\` (sem --skip-project).`);
|
|
213
|
+
p.log.warn(`${CONFIG_FILE}: sem project.id — pulei. Rode \`npx @spec-wave/cli@latest init\` (sem --skip-project).`);
|
|
214
214
|
} else {
|
|
215
215
|
const tk = await getToken();
|
|
216
216
|
if (!tk) {
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Movimentos de board do `implement` — decisão pura + executor não-fatal.
|
|
2
|
+
//
|
|
3
|
+
// O implement passa a mover os cards DETERMINISTICAMENTE (em código, não em
|
|
4
|
+
// prosa para o LLM seguir): no início a Feature/Story vão para Desenvolvimento
|
|
5
|
+
// (In Progress) e, no sucesso do spec-kit, as Tasks vão para Done e a Story
|
|
6
|
+
// para Code Review — mesma semântica de `task start`/`task done`/`story review`
|
|
7
|
+
// e do que o contexto em prosa sempre pediu. Falha de board NUNCA derruba a
|
|
8
|
+
// implementação (warn e segue).
|
|
9
|
+
|
|
10
|
+
import * as p from '@clack/prompts';
|
|
11
|
+
import { loadProjectConfig, resolveField, advanceToStage, setItemStatus } from './board.mjs';
|
|
12
|
+
import {
|
|
13
|
+
STAGE_DEVELOPMENT, STAGE_CODE_REVIEW, STAGE_DONE,
|
|
14
|
+
PROGRESS_TODO, PROGRESS_IN_PROGRESS, PROGRESS_DONE,
|
|
15
|
+
} from '../config.mjs';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Decide os movimentos de board de uma fase do implement. Pura, sem I/O.
|
|
19
|
+
*
|
|
20
|
+
* @param {'start'|'success'} phase
|
|
21
|
+
* @param {{ feature?: {nodeId:string,number:number}|null,
|
|
22
|
+
* story?: {nodeId:string,number:number}|null,
|
|
23
|
+
* tasks?: Array<{nodeId?:string,number:number}> }} refs
|
|
24
|
+
* @returns {Array<{nodeId:string, label:string, stage:string, status:string,
|
|
25
|
+
* statusFallback:boolean}>}
|
|
26
|
+
* statusFallback: se a Etapa já estiver adiante (advanceToStage devolve
|
|
27
|
+
* false), ainda assim alinhar o Status — usado nos movimentos de início
|
|
28
|
+
* (ex.: Feature já em Desenvolvimento volta a mostrar In Progress).
|
|
29
|
+
*/
|
|
30
|
+
export function planBoardMoves(phase, {
|
|
31
|
+
feature = null, story = null, tasks = [],
|
|
32
|
+
// Status das Tasks na fase start: Todo quando enfileiradas atrás de uma
|
|
33
|
+
// Story; In Progress quando a própria Task é o item sendo implementado.
|
|
34
|
+
tasksStartStatus = PROGRESS_TODO,
|
|
35
|
+
} = {}) {
|
|
36
|
+
const moves = [];
|
|
37
|
+
if (phase === 'start') {
|
|
38
|
+
if (feature?.nodeId) {
|
|
39
|
+
moves.push({ nodeId: feature.nodeId, label: `Feature #${feature.number}`,
|
|
40
|
+
stage: STAGE_DEVELOPMENT, status: PROGRESS_IN_PROGRESS, statusFallback: true });
|
|
41
|
+
}
|
|
42
|
+
if (story?.nodeId) {
|
|
43
|
+
moves.push({ nodeId: story.nodeId, label: `Story #${story.number}`,
|
|
44
|
+
stage: STAGE_DEVELOPMENT, status: PROGRESS_IN_PROGRESS, statusFallback: true });
|
|
45
|
+
}
|
|
46
|
+
for (const t of tasks) {
|
|
47
|
+
if (!t?.nodeId) continue;
|
|
48
|
+
moves.push({ nodeId: t.nodeId, label: `Task #${t.number}`,
|
|
49
|
+
stage: STAGE_DEVELOPMENT, status: tasksStartStatus,
|
|
50
|
+
statusFallback: tasksStartStatus === PROGRESS_IN_PROGRESS });
|
|
51
|
+
}
|
|
52
|
+
} else if (phase === 'success') {
|
|
53
|
+
for (const t of tasks) {
|
|
54
|
+
if (!t?.nodeId) continue;
|
|
55
|
+
moves.push({ nodeId: t.nodeId, label: `Task #${t.number}`,
|
|
56
|
+
stage: STAGE_DONE, status: PROGRESS_DONE, statusFallback: false });
|
|
57
|
+
}
|
|
58
|
+
if (story?.nodeId) {
|
|
59
|
+
moves.push({ nodeId: story.nodeId, label: `Story #${story.number}`,
|
|
60
|
+
stage: STAGE_CODE_REVIEW, status: PROGRESS_TODO, statusFallback: false });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return moves;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Executa os movimentos no Projects v2. Best-effort: qualquer falha vira
|
|
68
|
+
* warn e a implementação continua. Usa PROJECT_TOKEN quando definido (PAT
|
|
69
|
+
* com scope de Project em org — mesmo padrão do code-review/qa).
|
|
70
|
+
*/
|
|
71
|
+
export async function applyBoardMoves({ token, moves, cwd = process.cwd(), log = p.log }) {
|
|
72
|
+
if (!moves || moves.length === 0) return;
|
|
73
|
+
try {
|
|
74
|
+
const { project, error } = loadProjectConfig({ cwd });
|
|
75
|
+
if (error) {
|
|
76
|
+
log.warn(`board: ${error} — Etapas não atualizadas.`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const projectToken = process.env.PROJECT_TOKEN || token;
|
|
80
|
+
const etapaField = await resolveField(projectToken, project, 'Etapa').catch(() => null);
|
|
81
|
+
const statusField = await resolveField(projectToken, project, 'Status').catch(() => null);
|
|
82
|
+
if (!etapaField?.id) {
|
|
83
|
+
log.warn('board: campo Etapa não resolvido — Etapas não atualizadas.');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
for (const m of moves) {
|
|
87
|
+
try {
|
|
88
|
+
const advanced = await advanceToStage(
|
|
89
|
+
projectToken, project, etapaField, statusField, m.nodeId, m.stage, m.status);
|
|
90
|
+
if (advanced) {
|
|
91
|
+
log.info(`board: ${m.label} → ${m.stage} (${m.status})`);
|
|
92
|
+
} else if (m.statusFallback && statusField?.id) {
|
|
93
|
+
// Etapa já está nessa fase ou adiante: ao menos alinha o Status.
|
|
94
|
+
await setItemStatus(projectToken, project, statusField, m.nodeId, m.status);
|
|
95
|
+
log.info(`board: ${m.label} já em ${m.stage}+ — Status → ${m.status}`);
|
|
96
|
+
}
|
|
97
|
+
} catch (err) {
|
|
98
|
+
log.warn(`board: falha ao mover ${m.label}: ${err.message}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
log.warn(`board: indisponível (${err.message}) — implementação segue.`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -4,6 +4,7 @@ description: "Use when the user wants to set up a spec-driven GitHub workflow, c
|
|
|
4
4
|
argument-hint: "[info|setup|update|doctor|issue|feature|spec|plan|ready|decompose|order|implement|task|story|uninstall|rfc|fix-pr] [target]"
|
|
5
5
|
user-invocable: true
|
|
6
6
|
allowed-tools:
|
|
7
|
+
- Bash(npx @spec-wave/cli@latest *)
|
|
7
8
|
- Bash(npx @spec-wave/cli *)
|
|
8
9
|
- Bash(gh issue *)
|
|
9
10
|
- Bash(gh project *)
|
|
@@ -27,18 +28,18 @@ Este skill guia o usuário pelo fluxo spec-driven definido no RFC-001.
|
|
|
27
28
|
|
|
28
29
|
> **Antes de responder a qualquer sub-comando**, leia o arquivo `rfc/rfc-integrate-spec-kit-into-kanban.md` se ele existir no diretório atual, para embasar suas respostas no processo real da equipe.
|
|
29
30
|
|
|
30
|
-
> **Verifique se esta skill está atualizada:** logo no topo deste arquivo há um banner `spec-wave skill vX.Y.Z` (inserido na instalação). Compare com `npx @spec-wave/cli --version`. Se a CLI for **mais recente** (ou o banner estiver ausente = instalada por versão antiga), esta skill está desatualizada — avise o usuário e sugira `npx @spec-wave/cli update` (detecta e atualiza só o que mudou: skill, `.spec-wave.json` e workflows/labels do repo) ou, para atualizar só a skill, `npx @spec-wave/cli install-skill --force`. A skill é uma cópia estática e **não** acompanha o `npx` sozinha.
|
|
31
|
+
> **Verifique se esta skill está atualizada:** logo no topo deste arquivo há um banner `spec-wave skill vX.Y.Z` (inserido na instalação). Compare com `npx @spec-wave/cli@latest --version`. Se a CLI for **mais recente** (ou o banner estiver ausente = instalada por versão antiga), esta skill está desatualizada — avise o usuário e sugira `npx @spec-wave/cli@latest update` (detecta e atualiza só o que mudou: skill, `.spec-wave.json` e workflows/labels do repo) ou, para atualizar só a skill, `npx @spec-wave/cli@latest install-skill --force`. A skill é uma cópia estática e **não** acompanha o `npx` sozinha.
|
|
31
32
|
|
|
32
33
|
---
|
|
33
34
|
|
|
34
35
|
## Detecção de configuração (faça isto primeiro, sempre)
|
|
35
36
|
|
|
36
|
-
Antes de qualquer sub-comando, leia o arquivo `.spec-wave.json` na raiz do repositório atual (use o tool Read). Esse arquivo é gravado pelo `npx @spec-wave/cli init` e é a fonte de estado persistente entre sessões.
|
|
37
|
+
Antes de qualquer sub-comando, leia o arquivo `.spec-wave.json` na raiz do repositório atual (use o tool Read). Esse arquivo é gravado pelo `npx @spec-wave/cli@latest init` e é a fonte de estado persistente entre sessões.
|
|
37
38
|
|
|
38
39
|
- **Se existir**, o spec-wave já foi configurado. Use seus campos para contextualizar as respostas, sem perguntar de novo:
|
|
39
40
|
- `owner`/`repo` → repositório alvo dos comandos `gh`
|
|
40
41
|
- `project.url` / `project.title` → o GitHub Project a referenciar
|
|
41
|
-
- `version` → versão da CLI usada no `init` (compare com `npx @spec-wave/cli --version`; se divergir, sugira `npx @spec-wave/cli refresh --config` para atualizar o arquivo, ou re-rodar o `init` para atualizar workflows/labels)
|
|
42
|
+
- `version` → versão da CLI usada no `init` (compare com `npx @spec-wave/cli@latest --version`; se divergir, sugira `npx @spec-wave/cli@latest refresh --config` para atualizar o arquivo, ou re-rodar o `init` para atualizar workflows/labels)
|
|
42
43
|
- `initializedAt` → quando foi configurado
|
|
43
44
|
Não rode `/spec-wave setup` de novo a menos que o usuário peça explicitamente.
|
|
44
45
|
- **Se não existir**, o repositório provavelmente ainda não foi configurado. Sugira começar por `/spec-wave setup`.
|
|
@@ -86,13 +87,13 @@ Labels de **estado** (gravadas pelas automações — **não** são gatilhos, n
|
|
|
86
87
|
- `spec-wave:critique-failed` → a crítica adversarial apontou contradições **graves** nos documentos; **bloqueia** o `spec-wave:ready` até ser removida (veja *Crítica adversarial* abaixo)
|
|
87
88
|
- `spec-wave:decomposed` → a Feature/RFC já foi decomposta; o `decompose` pula silenciosamente enquanto ela existir (veja *Guard de idempotência* abaixo)
|
|
88
89
|
|
|
89
|
-
A etapa **🚧 Desenvolvimento** é coberta pelo comando **local** `npx @spec-wave/cli implement <número>` (não é uma label/Action): lê uma Story ou Task e aciona o spec-kit para implementar. Veja `/spec-wave implement`.
|
|
90
|
+
A etapa **🚧 Desenvolvimento** é coberta pelo comando **local** `npx @spec-wave/cli@latest implement <número>` (não é uma label/Action): lê uma **Feature** (todas as Stories pendentes, em ordem de dependência), uma Story ou uma Task e aciona o spec-kit para implementar. Veja `/spec-wave implement`.
|
|
90
91
|
|
|
91
92
|
---
|
|
92
93
|
|
|
93
94
|
## Referência da CLI (conheça os parâmetros ANTES de executar)
|
|
94
95
|
|
|
95
|
-
Esta skill é um **wrapper** da CLI `@spec-wave/cli`, sempre invocada como `npx @spec-wave/cli <comando>`. Regra de ouro: **nunca rode um comando sem os parâmetros que ele aceita** esperando que ele pergunte — colete os valores com o usuário e passe via flags. Em especial, **`init` sem `--repo` abre um wizard interativo (@clack/prompts) que a skill NÃO consegue dirigir** — sempre passe `--repo`.
|
|
96
|
+
Esta skill é um **wrapper** da CLI `@spec-wave/cli`, sempre invocada como `npx @spec-wave/cli@latest <comando>`. Regra de ouro: **nunca rode um comando sem os parâmetros que ele aceita** esperando que ele pergunte — colete os valores com o usuário e passe via flags. Em especial, **`init` sem `--repo` abre um wizard interativo (@clack/prompts) que a skill NÃO consegue dirigir** — sempre passe `--repo`.
|
|
96
97
|
|
|
97
98
|
### `@spec-wave/cli init` — configura o repositório
|
|
98
99
|
| Flag | Tipo | Descrição |
|
|
@@ -169,14 +170,14 @@ Mesmas flags do `issue` (exceto `--type`, fixo em `feature`). Mantido para o flu
|
|
|
169
170
|
> - `generate-spec` / `generate-plan` → **apenas Features**. Para **Spike, RFC e Bug** a geração é **pulada** (o Action remove a label e comenta) — esses tipos não usam spec/plan.
|
|
170
171
|
> - `decompose` → **Feature** (gera Stories + Tasks) e **RFC** (gera **Tasks** diretamente, sem Stories). Para outros tipos, o Action recusa.
|
|
171
172
|
|
|
172
|
-
### `@spec-wave/cli implement` — aciona o spec-kit para uma Story ou Task (comando LOCAL)
|
|
173
|
+
### `@spec-wave/cli implement` — aciona o spec-kit para uma Feature, Story ou Task (comando LOCAL)
|
|
173
174
|
| Flag/Arg | Tipo | Descrição |
|
|
174
175
|
|----------|------|-----------|
|
|
175
|
-
| `<issue>` | string (obrigatório) | Número da issue (Story ou Task), ex.: `12` ou `#12`. Argumento posicional. |
|
|
176
|
+
| `<issue>` | string (obrigatório) | Número da issue (Feature, Story ou Task), ex.: `12` ou `#12`. Argumento posicional. |
|
|
176
177
|
| `--feature-dir <path>` | string | Caminho `docs/features/<slug>` para anexar `spec.md`/`plan.md` como contexto (sobrescreve a resolução automática). |
|
|
177
178
|
| `--dry-run` | flag | Monta o contexto e imprime o comando do spec-kit **sem executar**. |
|
|
178
179
|
|
|
179
|
-
> Diferente dos quatro acima, `implement` roda **localmente** (lê `.spec-wave.json`, como `issue`), não por Action. Detecta o tipo da issue: **Story** → coleta todas as Tasks (sub-issues) e aciona o spec-kit uma única vez; **Task** → só aquela task. Monta o contexto em `.spec-wave/implement-<n>.md` e chama o comando configurado em `specKit.command` (no `.spec-wave.json`) ou na env `SPEC_WAVE_IMPLEMENT_CMD`. Placeholders disponíveis no template: `{tasksFile} {specFile} {planFile} {issue} {type} {title}`. Se nada estiver configurado, ele apenas monta o contexto e mostra como configurar (não executa). O contexto inclui os **comentários da issue**, um **digest do código recente** e um **aviso de dependências pendentes** quando a issue depende (linha `Depende de: #N` ou relação nativa *blocked by*) de outra que ainda não foi concluída — nesse caso, confirme com o usuário antes de seguir. Inclui também instruções para o agente implementar as Tasks **sequencialmente, uma por vez** (nunca duas com Status "In Progress" ao mesmo tempo): cada Task usa o **Status** (In Progress) *dentro* da Etapa 🚧 Desenvolvimento e, **ao concluir, avança para a Etapa 🎉 Done com Status Done**. **Ao concluir toda a Story**: fazer o commit, abrir o PR e **avançar a Etapa da Story para 👀 Code Review** (Status → Todo) — as Tasks já estão em 🎉 Done. A **Feature só avança** para Code Review quando **TODAS as suas Stories** já estiverem em Code Review — enquanto houver Story pendente, a Feature fica em 🚧 Desenvolvimento. Etapa só avança (nunca volta); Status mede o progresso dentro da etapa.
|
|
180
|
+
> Diferente dos quatro acima, `implement` roda **localmente** (lê `.spec-wave.json`, como `issue`), não por Action. Detecta o tipo da issue: **Feature** → lista as Stories (sub-issues), **ordena topologicamente pelas dependências** (`Depende de:` + *blocked by*), **pula** as já em 👀 Code Review+ (listadas no contexto como "não tocar") e monta **um único** contexto com todas as pendentes (cada uma com suas Tasks), acionando o spec-kit **uma vez** com `{issue}/{type}/{title}` da Feature — **ciclo de dependências entre Stories pendentes aborta o comando (exit 1)**; **Story** → coleta todas as Tasks (sub-issues) e aciona o spec-kit uma única vez; **Task** → só aquela task. Monta o contexto em `.spec-wave/implement-<n>.md` e chama o comando configurado em `specKit.command` (no `.spec-wave.json`) ou na env `SPEC_WAVE_IMPLEMENT_CMD`. Placeholders disponíveis no template: `{tasksFile} {specFile} {planFile} {issue} {type} {title}`. Se nada estiver configurado, ele apenas monta o contexto e mostra como configurar (não executa). O contexto inclui os **comentários da issue**, um **digest do código recente** e um **aviso de dependências pendentes** quando a issue depende (linha `Depende de: #N` ou relação nativa *blocked by*) de outra que ainda não foi concluída — nesse caso, confirme com o usuário antes de seguir. Inclui também instruções para o agente implementar as Tasks **sequencialmente, uma por vez** (nunca duas com Status "In Progress" ao mesmo tempo): cada Task usa o **Status** (In Progress) *dentro* da Etapa 🚧 Desenvolvimento e, **ao concluir, avança para a Etapa 🎉 Done com Status Done**. **Ao concluir toda a Story**: fazer o commit, abrir o PR e **avançar a Etapa da Story para 👀 Code Review** (Status → Todo) — as Tasks já estão em 🎉 Done. A **Feature só avança** para Code Review quando **TODAS as suas Stories** já estiverem em Code Review — enquanto houver Story pendente, a Feature fica em 🚧 Desenvolvimento. Etapa só avança (nunca volta); Status mede o progresso dentro da etapa.
|
|
180
181
|
|
|
181
182
|
### `@spec-wave/cli doctor` — preflight de auth e configuração (comando LOCAL)
|
|
182
183
|
Sem flags. Roda um checklist de diagnóstico no repositório atual: token GitHub (e a fonte dele), escopos (`repo`, `project`, `workflow` — com degradação para checks funcionais em fine-grained PATs), conta ativa do `gh` vs. owner, `.spec-wave.json` (campos e sincronia com o Project real), acesso ao repositório, configuração de IA (provider/modelo/`ai.models` + secrets do Actions), **spec-kit** (`specKit.command` / env `SPEC_WAVE_IMPLEMENT_CMD` — se ausente, avisa e sugere exemplos por agente: Claude Code, opencode, Codex, Copilot CLI, Kiro CLI, Qwen Code) e presença dos workflows.
|
|
@@ -228,7 +229,8 @@ O evento `labeled` pode redisparar (re-add da label, retry de runner). Para não
|
|
|
228
229
|
|
|
229
230
|
O `decompose` grava nas Stories geradas uma linha **`Depende de: #N, #M`** no corpo e cria a relação nativa *blocked by* do GitHub. Essas dependências alimentam:
|
|
230
231
|
- `spec-wave order <feature>` → ordem topológica de execução;
|
|
231
|
-
- `spec-wave implement <
|
|
232
|
+
- `spec-wave implement <feature>` → Stories pendentes implementadas **nessa ordem**; **ciclo de dependências → erro** (corrija as linhas `Depende de:`); dependências **externas** abertas viram aviso no contexto;
|
|
233
|
+
- `spec-wave implement <n>` (Story/Task) → **aviso** no contexto quando uma dependência ainda não está concluída (confirme com o usuário antes de implementar fora de ordem).
|
|
232
234
|
|
|
233
235
|
Não apague a linha `Depende de:` ao editar o corpo de uma Story; para mudar dependências, edite a linha (e/ou a relação *blocked by*).
|
|
234
236
|
|
|
@@ -260,12 +262,12 @@ Edite o bloco `ai` no `.spec-wave.json` (e commite) — o `doctor` mostra o prov
|
|
|
260
262
|
Mostra se o repositório atual já foi configurado com o spec-wave.
|
|
261
263
|
|
|
262
264
|
**Passos:**
|
|
263
|
-
1. Execute: `npx @spec-wave/cli info`
|
|
265
|
+
1. Execute: `npx @spec-wave/cli@latest info`
|
|
264
266
|
2. **Se o repositório estiver inicializado**, o comando mostra os dados do `.spec-wave.json` (owner/repo, project, versão da CLI, data). Apresente essas informações ao usuário.
|
|
265
267
|
3. **Se NÃO estiver inicializado**, pergunte ao usuário: "Este repositório ainda não foi configurado com o spec-wave. Quer rodar o `init` agora?"
|
|
266
268
|
- Se sim → siga o fluxo de `/spec-wave setup`.
|
|
267
269
|
- Se não → encerre sem alterar nada.
|
|
268
|
-
4. **Se a saída indicar skill pendente** (aviso "Skill pendente de instalação/atualização" ou, no `--json`, `skill.installNeeded: true`), pergunte ao usuário se quer instalar/atualizar agora: skill `ausente` → `npx @spec-wave/cli install-skill`; skill `desatualizada` → `npx @spec-wave/cli update` (atualiza tudo que ficou para trás). Lembre-o de recarregar o agente depois.
|
|
270
|
+
4. **Se a saída indicar skill pendente** (aviso "Skill pendente de instalação/atualização" ou, no `--json`, `skill.installNeeded: true`), pergunte ao usuário se quer instalar/atualizar agora: skill `ausente` → `npx @spec-wave/cli@latest install-skill`; skill `desatualizada` → `npx @spec-wave/cli@latest update` (atualiza tudo que ficou para trás). Lembre-o de recarregar o agente depois.
|
|
269
271
|
|
|
270
272
|
---
|
|
271
273
|
|
|
@@ -276,12 +278,12 @@ Traz tudo para a versão atual da CLI, atualizando **só o que mudou**: a skill
|
|
|
276
278
|
**Passos:**
|
|
277
279
|
1. **Sempre comece com `--dry-run`** para inspecionar o que está desatualizado sem alterar nada:
|
|
278
280
|
```bash
|
|
279
|
-
npx @spec-wave/cli update --dry-run
|
|
281
|
+
npx @spec-wave/cli@latest update --dry-run
|
|
280
282
|
```
|
|
281
283
|
2. Mostre ao usuário o resumo (skill / config / arquivos do repo / labels que divergiram). Se **nada** estiver desatualizado, informe que já está tudo na versão atual e encerre.
|
|
282
284
|
3. Se o usuário aprovar, aplique:
|
|
283
285
|
```bash
|
|
284
|
-
npx @spec-wave/cli update --yes
|
|
286
|
+
npx @spec-wave/cli@latest update --yes
|
|
285
287
|
```
|
|
286
288
|
- Escopos podem ser limitados com `--skip-skill`, `--skip-config`, `--skip-repo`.
|
|
287
289
|
- Atualizações de **arquivos do repo** são commitadas no remoto; o **`.spec-wave.json`** é local (lembre o usuário de commitá-lo).
|
|
@@ -291,17 +293,17 @@ Traz tudo para a versão atual da CLI, atualizando **só o que mudou**: a skill
|
|
|
291
293
|
|
|
292
294
|
### `/spec-wave setup`
|
|
293
295
|
|
|
294
|
-
Configura o spec-wave no repositório. Você dirige o `init` com flags — **nunca rode `npx @spec-wave/cli init` sem `--repo`** (abre o wizard interativo que você não controla).
|
|
296
|
+
Configura o spec-wave no repositório. Você dirige o `init` com flags — **nunca rode `npx @spec-wave/cli@latest init` sem `--repo`** (abre o wizard interativo que você não controla).
|
|
295
297
|
|
|
296
298
|
**Passos:**
|
|
297
|
-
1. **Já configurado?** Leia `.spec-wave.json` (ou rode `npx @spec-wave/cli info`). Se existir, avise (mostre `project.url` e `version`) e confirme com o usuário antes de reconfigurar.
|
|
299
|
+
1. **Já configurado?** Leia `.spec-wave.json` (ou rode `npx @spec-wave/cli@latest info`). Se existir, avise (mostre `project.url` e `version`) e confirme com o usuário antes de reconfigurar.
|
|
298
300
|
2. **Descubra o repositório alvo** (parâmetro `--repo`): rode `gh repo view --json nameWithOwner -q .nameWithOwner` para obter `owner/repo` do repo atual. Confirme com o usuário; se não houver remote, pergunte o `owner/repo`.
|
|
299
301
|
3. **Pergunte o título do Project** (parâmetro `--project-title`). Ofereça o default `<repo> — Spec Wave` e aceite-o se o usuário não tiver preferência.
|
|
300
302
|
4. **Cheque o auth:** `gh auth status`. Se faltarem os escopos `project,repo,workflow`, oriente o usuário a rodar ele mesmo `gh auth refresh --scopes project,repo,workflow` (comando interativo — o usuário executa, não você).
|
|
301
|
-
5. **(Opcional) Pré-visualize** antes de aplicar: `npx @spec-wave/cli init --repo <owner/repo> --dry-run`.
|
|
303
|
+
5. **(Opcional) Pré-visualize** antes de aplicar: `npx @spec-wave/cli@latest init --repo <owner/repo> --dry-run`.
|
|
302
304
|
6. **Execute com os parâmetros coletados:**
|
|
303
305
|
```bash
|
|
304
|
-
npx @spec-wave/cli init --repo <owner/repo> --project-title "<título>"
|
|
306
|
+
npx @spec-wave/cli@latest init --repo <owner/repo> --project-title "<título>"
|
|
305
307
|
```
|
|
306
308
|
Use `--skip-project` / `--skip-labels` / `--skip-files` **apenas** para re-rodar uma fase específica que falhou antes.
|
|
307
309
|
7. O `init` cria o Project, as labels, os workflows, um **scaffold de `.github/config/tech_context.yml`** (só se ainda não existir) e grava `.spec-wave.json`. Oriente o usuário a fazer `git pull` para trazer os arquivos ao checkout local.
|
|
@@ -322,7 +324,7 @@ Crie um work item tipado (Initiative/Epic/Feature/Story/Task/...) já adicionado
|
|
|
322
324
|
1. Pergunte ao usuário: tipo (initiative/epic/feature/story/task/...), título (sem prefixo), descrição e se há uma issue **pai** (número). **Prioridade e área são opcionais**: só as inclua se o usuário pedir explicitamente. **Nunca atribua uma prioridade por conta própria** — se o usuário não informou, **omita `--priority`** e a prioridade fica `null` (sem prioridade) no board.
|
|
323
325
|
2. Execute o comando com os parâmetros coletados (inclua **apenas** as flags que o usuário forneceu):
|
|
324
326
|
```bash
|
|
325
|
-
npx @spec-wave/cli issue \
|
|
327
|
+
npx @spec-wave/cli@latest issue \
|
|
326
328
|
--type "<tipo>" \
|
|
327
329
|
--title "<título>" \
|
|
328
330
|
--body "<descrição>" \
|
|
@@ -330,7 +332,7 @@ Crie um work item tipado (Initiative/Epic/Feature/Story/Task/...) já adicionado
|
|
|
330
332
|
--priority "<prioridade>" \ # opcional — só se o usuário pediu; caso contrário OMITA (prioridade fica null)
|
|
331
333
|
--parent "<número-do-pai>" # opcional
|
|
332
334
|
```
|
|
333
|
-
Para Features, pode usar o atalho `npx @spec-wave/cli feature --title ...` (equivale a `--type feature`).
|
|
335
|
+
Para Features, pode usar o atalho `npx @spec-wave/cli@latest feature --title ...` (equivale a `--type feature`).
|
|
334
336
|
A CLI cria a issue (label de tipo — e de prioridade **apenas se `--priority` for informado**), vincula como sub-issue do parent, adiciona ao Project e define Etapa = 📥 Backlog + Work Item Type + Area (+ Priority só se informada). **Não use `gh issue create`** (não adiciona ao board nem vincula o parent).
|
|
335
337
|
3. Informe o número criado e o vínculo com o pai (se houver).
|
|
336
338
|
4. Para Features: "Quando quiser iniciar, mova para **📋 Spec** e use `/spec-wave spec <número>` para gerar a especificação funcional (o plano técnico vem depois)".
|
|
@@ -343,8 +345,8 @@ Remove a configuração do spec-wave do repositório (labels, arquivos `.github`
|
|
|
343
345
|
|
|
344
346
|
**Passos:**
|
|
345
347
|
1. Confirme com o usuário que ele quer remover (a ação remove labels e faz commits removendo os workflows).
|
|
346
|
-
2. Mostre antes o que será removido com `npx @spec-wave/cli uninstall --dry-run`.
|
|
347
|
-
3. Execute `npx @spec-wave/cli uninstall` (a CLI pede confirmação; use `--yes` só se o usuário já confirmou).
|
|
348
|
+
2. Mostre antes o que será removido com `npx @spec-wave/cli@latest uninstall --dry-run`.
|
|
349
|
+
3. Execute `npx @spec-wave/cli@latest uninstall` (a CLI pede confirmação; use `--yes` só se o usuário já confirmou).
|
|
348
350
|
4. Lembre o usuário de excluir o **GitHub Project** manualmente, se desejar — a CLI não o apaga de propósito.
|
|
349
351
|
|
|
350
352
|
---
|
|
@@ -388,7 +390,7 @@ O plano técnico segue o schema do RFC-002 §3.2: **Estratégia Técnica** (com
|
|
|
388
390
|
|
|
389
391
|
### Tech Context (`.github/config/tech_context.yml`)
|
|
390
392
|
|
|
391
|
-
Fonte de verdade estática da stack do sistema (RFC-002 §4). O `generate-plan` lê este arquivo para embasar o plano técnico e usar **APENAS** as tecnologias/serviços nele declarados — sem ele, o plano fica genérico e pode inventar APIs inexistentes. O `npx @spec-wave/cli init` gera um **scaffold de exemplo** que **deve ser adaptado** à stack real. Use este fluxo quando o arquivo estiver ausente ou desatualizado.
|
|
393
|
+
Fonte de verdade estática da stack do sistema (RFC-002 §4). O `generate-plan` lê este arquivo para embasar o plano técnico e usar **APENAS** as tecnologias/serviços nele declarados — sem ele, o plano fica genérico e pode inventar APIs inexistentes. O `npx @spec-wave/cli@latest init` gera um **scaffold de exemplo** que **deve ser adaptado** à stack real. Use este fluxo quando o arquivo estiver ausente ou desatualizado.
|
|
392
394
|
|
|
393
395
|
**Como ajudar a criar (quando não existir):**
|
|
394
396
|
|
|
@@ -473,33 +475,35 @@ Para qualquer outro tipo (Spike, Bug, Story, Task, …) o Action **recusa** e co
|
|
|
473
475
|
gh issue edit <número> --add-label "spec-wave:decompose"
|
|
474
476
|
```
|
|
475
477
|
3. Informe: "Decomposição iniciada — Feature gera Stories+Tasks; RFC gera Tasks."
|
|
476
|
-
4. Após a conclusão, as issues filhas aparecerão como comentário na issue pai, junto com o comentário 🔎 da crítica adversarial. A issue pai e as Stories/Tasks criadas entram no board na Etapa **✅ Ready** (Status Todo; a Etapa nunca retrocede — itens já adiante não são tocados). As Stories geradas trazem a linha `Depende de: #N` (+ relação *blocked by*) — use `npx @spec-wave/cli order <número>` para ver a ordem de execução.
|
|
478
|
+
4. Após a conclusão, as issues filhas aparecerão como comentário na issue pai, junto com o comentário 🔎 da crítica adversarial. A issue pai e as Stories/Tasks criadas entram no board na Etapa **✅ Ready** (Status Todo; a Etapa nunca retrocede — itens já adiante não são tocados). As Stories geradas trazem a linha `Depende de: #N` (+ relação *blocked by*) — use `npx @spec-wave/cli@latest order <número>` para ver a ordem de execução.
|
|
477
479
|
5. A issue recebe a label `spec-wave:decomposed` (guard de idempotência): rodar de novo **não** duplica as issues. Para forçar um re-decompose, siga a seção *Guard de idempotência*.
|
|
478
480
|
|
|
479
481
|
---
|
|
480
482
|
|
|
481
483
|
### `/spec-wave implement <número-da-issue>`
|
|
482
484
|
|
|
483
|
-
Aciona o spec-kit para implementar uma **Story** (todas as suas Tasks) ou uma **Task** isolada. Comando **local** (etapa 🚧 Desenvolvimento) — não usa label/Action.
|
|
485
|
+
Aciona o spec-kit para implementar uma **Feature** (todas as Stories pendentes, em ordem de dependência), uma **Story** (todas as suas Tasks) ou uma **Task** isolada. Comando **local** (etapa 🚧 Desenvolvimento) — não usa label/Action.
|
|
484
486
|
|
|
485
|
-
**Pré-requisitos:** o repositório atual precisa estar inicializado (`.spec-wave.json` presente) e a issue deve ser do tipo Story ou Task. Para executar de fato (fora do `--dry-run`), o spec-kit precisa estar configurado via `specKit.command` no `.spec-wave.json` ou a env `SPEC_WAVE_IMPLEMENT_CMD`.
|
|
487
|
+
**Pré-requisitos:** o repositório atual precisa estar inicializado (`.spec-wave.json` presente) e a issue deve ser do tipo Feature, Story ou Task. Para executar de fato (fora do `--dry-run`), o spec-kit precisa estar configurado via `specKit.command` no `.spec-wave.json` ou a env `SPEC_WAVE_IMPLEMENT_CMD`.
|
|
488
|
+
|
|
489
|
+
**Modo Feature:** o comando avalia as Stories da Feature — ordena topologicamente pelas dependências (`Depende de:` + *blocked by*), consulta a Etapa de cada uma no board e **pula as já implementadas** (👀 Code Review ou além). O contexto único (`.spec-wave/implement-<feature>.md`) traz as pendentes em ordem, cada uma com suas Tasks. **Ciclo de dependências entre Stories pendentes → o comando aborta** (corrija as linhas `Depende de:`; use `npx @spec-wave/cli@latest order <feature>` para visualizar). Story pendente sem Tasks → aborta pedindo decomposição. Todas implementadas → encerra sem acionar o spec-kit.
|
|
486
490
|
|
|
487
491
|
**Passos:**
|
|
488
492
|
1. Confirme que há `.spec-wave.json` no repo (senão, oriente `/spec-wave setup`).
|
|
489
|
-
2. **Sempre comece com `--dry-run`** para inspecionar o que será feito — detecção do tipo, lista de Tasks coletadas (
|
|
493
|
+
2. **Sempre comece com `--dry-run`** para inspecionar o que será feito — detecção do tipo, lista de Tasks coletadas (Story) ou a ordem/puladas/ciclos das Stories (Feature) e o comando do spec-kit que seria executado:
|
|
490
494
|
```bash
|
|
491
|
-
npx @spec-wave/cli implement <número> --dry-run
|
|
495
|
+
npx @spec-wave/cli@latest implement <número> --dry-run
|
|
492
496
|
```
|
|
493
497
|
3. Mostre ao usuário o contexto montado em `.spec-wave/implement-<número>.md` e o comando. Esse arquivo contém as **instruções de execução sequencial**: implemente as Tasks **uma por vez** — mova a task para **🚧 Desenvolvimento** só ao iniciá-la e para **🎉 Done** ao concluí-la, antes de passar para a próxima. **Nunca** coloque várias tasks em "in progress" ao mesmo tempo.
|
|
494
|
-
4. **Se você (agente) for implementar diretamente** (sem `specKit.command`): siga o contexto task por task. Para cada task: `npx @spec-wave/cli task start <n>` ao iniciar (Etapa 🚧 Desenvolvimento + Status In Progress) e `npx @spec-wave/cli task done <n>` ao concluir (Etapa 🎉 Done + Status Done) — **prefira esses comandos a mutações GraphQL/`gh` manuais**: eles embutem as regras do board (Etapa nunca retrocede; uma task In Progress por vez). Se o contexto trouxer **aviso de dependência pendente** (a issue depende de outra não concluída), confirme com o usuário antes de seguir. **Ao concluir toda a Story**: faça o commit, abra o PR e mova a Story com `npx @spec-wave/cli story review <n>` (Etapa 👀 Code Review, Status → Todo) — as Tasks já estão em 🎉 Done. A **Feature só avança** quando **TODAS as suas Stories** já estiverem em Code Review — se houver Story pendente, deixe a Feature em 🚧 Desenvolvimento. Lembre: Etapa só avança (nunca volta); Status é o progresso dentro da etapa.
|
|
498
|
+
4. **Se você (agente) for implementar diretamente** (sem `specKit.command`): siga o contexto task por task. Para cada task: `npx @spec-wave/cli@latest task start <n>` ao iniciar (Etapa 🚧 Desenvolvimento + Status In Progress) e `npx @spec-wave/cli@latest task done <n>` ao concluir (Etapa 🎉 Done + Status Done) — **prefira esses comandos a mutações GraphQL/`gh` manuais**: eles embutem as regras do board (Etapa nunca retrocede; uma task In Progress por vez). Se o contexto trouxer **aviso de dependência pendente** (a issue depende de outra não concluída), confirme com o usuário antes de seguir. **Ao concluir toda a Story**: faça o commit, abra o PR e mova a Story com `npx @spec-wave/cli@latest story review <n>` (Etapa 👀 Code Review, Status → Todo) — as Tasks já estão em 🎉 Done. A **Feature só avança** quando **TODAS as suas Stories** já estiverem em Code Review — se houver Story pendente, deixe a Feature em 🚧 Desenvolvimento. Lembre: Etapa só avança (nunca volta); Status é o progresso dentro da etapa.
|
|
495
499
|
5. Se o usuário aprovar e o spec-kit estiver configurado, rode sem `--dry-run`:
|
|
496
500
|
```bash
|
|
497
|
-
npx @spec-wave/cli implement <número>
|
|
501
|
+
npx @spec-wave/cli@latest implement <número>
|
|
498
502
|
```
|
|
499
503
|
- Se o spec-kit **não** estiver configurado, o comando só monta o contexto e mostra como configurar (`specKit.command` / `SPEC_WAVE_IMPLEMENT_CMD`). Ajude o usuário a definir o template (placeholders: `{tasksFile} {specFile} {planFile} {issue} {type} {title}`).
|
|
500
504
|
- Use `--feature-dir docs/features/<slug>` se a resolução automática da Feature falhar (a skill avisa com warning) e você quiser anexar `spec.md`/`plan.md` como contexto.
|
|
501
|
-
6. Se a issue **não** for Story nem Task (ex.:
|
|
502
|
-
7. Ao final (Tasks em **🎉 Done**, Story em **👀 Code Review**; a Feature só vai para Code Review quando a última Story concluir): confirme o resultado com o usuário e oriente a revisão
|
|
505
|
+
6. **No modo Feature**, siga o contexto Story a Story, na ordem listada: para cada Story pendente, implemente as Tasks com `task start`/`task done`, depois commit + PR + `npx @spec-wave/cli@latest story review <n>`; só então passe à próxima Story. Se a issue **não** for Feature, Story nem Task (ex.: Bug, Spike, Epic), o comando recusa. Feature **sem Stories** → rode `/spec-wave decompose` primeiro. **Ciclo de dependências** → corrija as linhas `Depende de:` (veja `spec-wave order`).
|
|
506
|
+
7. Ao final (Tasks em **🎉 Done**, Story em **👀 Code Review**; a Feature só vai para Code Review quando a última Story concluir — no modo Feature, isso acontece dentro da mesma execução): confirme o resultado com o usuário e oriente a revisão dos PRs.
|
|
503
507
|
|
|
504
508
|
---
|
|
505
509
|
|
|
@@ -24,7 +24,7 @@ jobs:
|
|
|
24
24
|
node-version: '24'
|
|
25
25
|
|
|
26
26
|
- name: Move Feature to Code Review
|
|
27
|
-
run: npx @spec-wave/cli code-review --pr-number ${{ github.event.pull_request.number }}
|
|
27
|
+
run: npx @spec-wave/cli@latest code-review --pr-number ${{ github.event.pull_request.number }}
|
|
28
28
|
env:
|
|
29
29
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
30
30
|
PROJECT_TOKEN: ${{ secrets.GH_PROJECT_TOKEN }}
|
|
@@ -29,7 +29,7 @@ jobs:
|
|
|
29
29
|
node-version: '24'
|
|
30
30
|
|
|
31
31
|
- name: Decompose into Stories and Tasks
|
|
32
|
-
run: npx @spec-wave/cli decompose --issue-number ${{ github.event.issue.number }}
|
|
32
|
+
run: npx @spec-wave/cli@latest decompose --issue-number ${{ github.event.issue.number }}
|
|
33
33
|
env:
|
|
34
34
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
35
35
|
PROJECT_TOKEN: ${{ secrets.GH_PROJECT_TOKEN }}
|
|
@@ -28,7 +28,7 @@ jobs:
|
|
|
28
28
|
node-version: '24'
|
|
29
29
|
|
|
30
30
|
- name: Generate plan.md
|
|
31
|
-
run: npx @spec-wave/cli generate-plan --issue-number ${{ github.event.issue.number }}
|
|
31
|
+
run: npx @spec-wave/cli@latest generate-plan --issue-number ${{ github.event.issue.number }}
|
|
32
32
|
env:
|
|
33
33
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
34
34
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
@@ -28,7 +28,7 @@ jobs:
|
|
|
28
28
|
node-version: '24'
|
|
29
29
|
|
|
30
30
|
- name: Generate spec.md
|
|
31
|
-
run: npx @spec-wave/cli generate-spec --issue-number ${{ github.event.issue.number }}
|
|
31
|
+
run: npx @spec-wave/cli@latest generate-spec --issue-number ${{ github.event.issue.number }}
|
|
32
32
|
env:
|
|
33
33
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
34
34
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
@@ -25,7 +25,7 @@ jobs:
|
|
|
25
25
|
node-version: '24'
|
|
26
26
|
|
|
27
27
|
- name: Move Feature to QA
|
|
28
|
-
run: npx @spec-wave/cli qa --pr-number ${{ github.event.pull_request.number }}
|
|
28
|
+
run: npx @spec-wave/cli@latest qa --pr-number ${{ github.event.pull_request.number }}
|
|
29
29
|
env:
|
|
30
30
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
31
31
|
PROJECT_TOKEN: ${{ secrets.GH_PROJECT_TOKEN }}
|
|
@@ -26,7 +26,7 @@ jobs:
|
|
|
26
26
|
node-version: '24'
|
|
27
27
|
|
|
28
28
|
- name: Validate spec and plan
|
|
29
|
-
run: npx @spec-wave/cli validate --issue-number ${{ github.event.issue.number }}
|
|
29
|
+
run: npx @spec-wave/cli@latest validate --issue-number ${{ github.event.issue.number }}
|
|
30
30
|
env:
|
|
31
31
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
32
32
|
GITHUB_REPOSITORY: ${{ github.repository }}
|