@spec-wave/cli 0.21.0 → 0.23.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/bin/spec-wave.mjs +2 -2
- package/package.json +1 -1
- package/src/agent/anthropic-agent.mjs +3 -1
- package/src/agent/errors.mjs +59 -13
- package/src/agent/openrouter-agent.mjs +5 -1
- package/src/api/github-graphql.mjs +63 -0
- package/src/api/github-rest.mjs +9 -2
- package/src/commands/decompose.mjs +82 -5
- package/src/commands/move.mjs +52 -0
- package/src/commands/order.mjs +200 -7
- package/src/commands/validate.mjs +39 -18
- package/src/config.mjs +9 -3
- package/src/lib/board.mjs +34 -1
- package/src/lib/bug-doc.mjs +71 -0
- package/src/lib/claude.mjs +23 -6
- package/src/lib/decomposition-doc.mjs +66 -14
- package/src/lib/dependencies.mjs +14 -4
- package/src/plugin/.claude-plugin/plugin.json +1 -1
- package/src/plugin/skills/decompose/SKILL.md +3 -3
- package/src/plugin/skills/order/SKILL.md +8 -4
- package/src/plugin/skills/ready/SKILL.md +1 -1
- package/src/templates/skill/SKILL.md +4 -4
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
// ## Story 1 — <título curto>
|
|
21
21
|
//
|
|
22
22
|
// **User story:** Como <perfil>, quero <objetivo>, para <benefício>
|
|
23
|
-
// **Depende de:** Story 1,
|
|
23
|
+
// **Depende de:** Story 1, #412 (ou "—" para nenhuma)
|
|
24
24
|
//
|
|
25
25
|
// <corpo livre, multi-linha>
|
|
26
26
|
//
|
|
@@ -162,6 +162,16 @@ function normalizeDependsOn(dependsOn, index) {
|
|
|
162
162
|
.sort((a, b) => a - b);
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
// Dependências para FORA da Feature: números de issue que já existem.
|
|
166
|
+
//
|
|
167
|
+
// A regra "só aponta para trás" NÃO se aplica a elas — ela existe para que a
|
|
168
|
+
// ordem de criação do apply seja topologicamente válida, e uma issue que já
|
|
169
|
+
// existe não é criada por este apply. O que vale aqui é ser inteiro positivo.
|
|
170
|
+
function normalizeDependsOnIssues(list) {
|
|
171
|
+
if (!Array.isArray(list)) return [];
|
|
172
|
+
return [...new Set(list.filter(n => Number.isInteger(n) && n > 0))].sort((a, b) => a - b);
|
|
173
|
+
}
|
|
174
|
+
|
|
165
175
|
/**
|
|
166
176
|
* Renderiza o decomposition.md (função PURA — testável).
|
|
167
177
|
*
|
|
@@ -223,12 +233,16 @@ export function renderDecompositionDoc({
|
|
|
223
233
|
(stories || []).forEach((story, i) => {
|
|
224
234
|
blocks.push(`## Story ${i + 1} — ${flatten(story?.title) || '(sem título)'}`);
|
|
225
235
|
const deps = normalizeDependsOn(story?.dependsOn, i);
|
|
236
|
+
// Irmãs primeiro (por índice), depois as externas (por número): a saída é
|
|
237
|
+
// canônica, então duas escritas do mesmo conteúdo dão o mesmo arquivo.
|
|
238
|
+
const externas = normalizeDependsOnIssues(story?.dependsOnIssues);
|
|
239
|
+
const refs = [...deps.map(d => `Story ${d + 1}`), ...externas.map(n => `#${n}`)];
|
|
226
240
|
// As DUAS linhas de campo saem SEMPRE, seguidas de linha em branco: é essa
|
|
227
241
|
// linha em branco que impede um corpo começando com "**Depende de:** …" de
|
|
228
242
|
// ser confundido com o campo.
|
|
229
243
|
const campos = [
|
|
230
244
|
`**User story:** ${flatten(story?.userStory) || '—'}`,
|
|
231
|
-
`**Depende de:** ${
|
|
245
|
+
`**Depende de:** ${refs.length ? refs.join(', ') : '—'}`,
|
|
232
246
|
];
|
|
233
247
|
const linhaIssue = issueLine(story);
|
|
234
248
|
if (linhaIssue) campos.push(linhaIssue);
|
|
@@ -276,28 +290,62 @@ function requireTitle(value, anchor) {
|
|
|
276
290
|
// "Story 1, Story 3" → [0, 2]. Linha ausente ou "—" → []. Referência a si mesma
|
|
277
291
|
// ou a uma story POSTERIOR é erro: num arquivo editado à mão, filtrar em
|
|
278
292
|
// silêncio (como se fazia com o JSON do modelo) esconderia o engano do humano.
|
|
293
|
+
// Só separadores e conjunções podem sobrar depois de extrair as referências.
|
|
294
|
+
// Sem esta checagem, "**Depende de:** #12 no sistema legado" viraria "depende da
|
|
295
|
+
// issue 12" e a prosa sumiria em silêncio — o erro que este parser existe para
|
|
296
|
+
// não cometer.
|
|
297
|
+
const DEPENDS_LEFTOVER_RE = /^[\s,;+&/–—-]*(?:\b(?:e|and)\b[\s,;+&/–—-]*)*$/i;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Interpreta o valor de "**Depende de:**" (função PURA).
|
|
301
|
+
*
|
|
302
|
+
* Duas formas convivem na mesma linha, e cada uma diz uma coisa diferente:
|
|
303
|
+
* • `Story N` — irmã, no MESMO documento, sempre para trás (é o que mantém a
|
|
304
|
+
* ordem de criação do apply topologicamente válida);
|
|
305
|
+
* • `#N` — issue que JÁ existe, de qualquer Feature. Sem ela, tudo que cruza a
|
|
306
|
+
* fronteira da Feature vivia na prosa, e nenhuma automação enxergava.
|
|
307
|
+
*
|
|
308
|
+
* @returns {{ siblings: number[], issues: number[] }} siblings 0-based
|
|
309
|
+
*/
|
|
279
310
|
function parseDependsValue(raw, index, anchor) {
|
|
280
|
-
if (raw === null || raw === undefined) return [];
|
|
311
|
+
if (raw === null || raw === undefined) return { siblings: [], issues: [] };
|
|
281
312
|
const value = raw.trim();
|
|
282
|
-
if (!value || EMPTY_VALUE_RE.test(value)) return [];
|
|
283
|
-
|
|
284
|
-
|
|
313
|
+
if (!value || EMPTY_VALUE_RE.test(value)) return { siblings: [], issues: [] };
|
|
314
|
+
|
|
315
|
+
const storyMatches = [...value.matchAll(/story[ \t]*(\d+)/gi)];
|
|
316
|
+
const issueMatches = [...value.matchAll(/#(\d+)/g)];
|
|
317
|
+
if (storyMatches.length === 0 && issueMatches.length === 0) {
|
|
318
|
+
throw invalid(
|
|
319
|
+
`não entendi "**Depende de:** ${value}" em ${anchor} — use "Story 1", "#412" ou "—"`
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
const resto = value
|
|
323
|
+
.replace(/story[ \t]*\d+/gi, '')
|
|
324
|
+
.replace(/#\d+/g, '');
|
|
325
|
+
if (!DEPENDS_LEFTOVER_RE.test(resto)) {
|
|
285
326
|
throw invalid(
|
|
286
|
-
`não entendi "**Depende de:** ${value}" em ${anchor} —
|
|
327
|
+
`não entendi "**Depende de:** ${value}" em ${anchor} — sobrou "${resto.trim()}". ` +
|
|
328
|
+
'Use só referências ("Story 1, #412") ou "—"'
|
|
287
329
|
);
|
|
288
330
|
}
|
|
289
|
-
|
|
290
|
-
|
|
331
|
+
|
|
332
|
+
const siblings = [];
|
|
333
|
+
for (const m of storyMatches) {
|
|
291
334
|
const n = parseInt(m[1], 10);
|
|
292
335
|
if (n < 1 || n - 1 >= index) {
|
|
293
336
|
throw invalid(
|
|
294
337
|
`${anchor} depende de "Story ${n}", que não é uma Story anterior a ela — ` +
|
|
295
|
-
'dependências só apontam para trás'
|
|
338
|
+
'dependências entre irmãs só apontam para trás (para outra Feature, use "#<issue>")'
|
|
296
339
|
);
|
|
297
340
|
}
|
|
298
|
-
if (!
|
|
341
|
+
if (!siblings.includes(n - 1)) siblings.push(n - 1);
|
|
342
|
+
}
|
|
343
|
+
const issues = [];
|
|
344
|
+
for (const m of issueMatches) {
|
|
345
|
+
const n = parseInt(m[1], 10);
|
|
346
|
+
if (n > 0 && !issues.includes(n)) issues.push(n);
|
|
299
347
|
}
|
|
300
|
-
return
|
|
348
|
+
return { siblings: siblings.sort((a, b) => a - b), issues: issues.sort((a, b) => a - b) };
|
|
301
349
|
}
|
|
302
350
|
|
|
303
351
|
// Tasks não têm campos de gramática além do título — só a linha `**Issue:** #N`
|
|
@@ -352,7 +400,8 @@ function splitStorySection(lines, anchor) {
|
|
|
352
400
|
* Interpreta o decomposition.md (função PURA — testável).
|
|
353
401
|
*
|
|
354
402
|
* Devolve exatamente a shape que o loop de criação de issues consome
|
|
355
|
-
* (`stories[].{title,userStory,body,dependsOn,tasks[]}`, com
|
|
403
|
+
* (`stories[].{title,userStory,body,dependsOn,dependsOnIssues,tasks[]}`, com
|
|
404
|
+
* dependsOn 0-based entre irmãs e dependsOnIssues em números de issue),
|
|
356
405
|
* mais os metadados do marcador e a âncora estável de cada item ("Story 3",
|
|
357
406
|
* "Task 3.2") — é a âncora que a crítica cita no comentário da issue.
|
|
358
407
|
*
|
|
@@ -473,7 +522,10 @@ export function parseDecompositionDoc(markdown) {
|
|
|
473
522
|
userStory,
|
|
474
523
|
body,
|
|
475
524
|
issue,
|
|
476
|
-
|
|
525
|
+
...(() => {
|
|
526
|
+
const { siblings, issues } = parseDependsValue(depends, doc.stories.length, anchor);
|
|
527
|
+
return { dependsOn: siblings, dependsOnIssues: issues };
|
|
528
|
+
})(),
|
|
477
529
|
tasks: [],
|
|
478
530
|
});
|
|
479
531
|
continue;
|
package/src/lib/dependencies.mjs
CHANGED
|
@@ -44,8 +44,14 @@ export function parseDependencies(body) {
|
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* Ordena Stories topologicamente pelas dependências (Kahn). Estável: entre as
|
|
47
|
-
* Stories liberadas ao mesmo tempo, vence a de menor number.
|
|
48
|
-
*
|
|
47
|
+
* Stories liberadas ao mesmo tempo, vence a de menor number.
|
|
48
|
+
*
|
|
49
|
+
* Dependência para FORA do conjunto (uma Story de outra Feature) não participa
|
|
50
|
+
* da ordenação — sem ela no conjunto não há como saber onde entra —, mas deixou
|
|
51
|
+
* de ser DESCARTADA: volta em `external`, para o chamador mostrar quem está
|
|
52
|
+
* bloqueado por fora. Antes ela era lida, filtrada aqui e ignorada de novo no
|
|
53
|
+
* aviso de fora-de-ordem: o dado entrava e sumia sem nenhuma mensagem, que é
|
|
54
|
+
* pior do que não aceitá-lo.
|
|
49
55
|
*
|
|
50
56
|
* Contrato: NUNCA lança. Retorna sempre `{ order, cycle }`:
|
|
51
57
|
* • sem ciclo → order = todos os numbers em ordem de execução, cycle = [];
|
|
@@ -54,15 +60,19 @@ export function parseDependencies(body) {
|
|
|
54
60
|
* O chamador decide se trata cycle.length > 0 como erro.
|
|
55
61
|
*
|
|
56
62
|
* @param {Array<{ number: number, dependsOn: number[] }>} stories
|
|
57
|
-
* @returns {{ order: number[], cycle: number[] }}
|
|
63
|
+
* @returns {{ order: number[], cycle: number[], external: Map<number, number[]> }}
|
|
64
|
+
* external: number da Story → dependências fora do conjunto
|
|
58
65
|
*/
|
|
59
66
|
export function orderStories(stories) {
|
|
60
67
|
const known = new Set(stories.map(s => s.number));
|
|
61
68
|
// indegree = quantas dependências INTERNAS ainda não resolvidas.
|
|
62
69
|
const indegree = new Map();
|
|
63
70
|
const dependents = new Map(); // number → numbers que dependem dele
|
|
71
|
+
const external = new Map();
|
|
64
72
|
for (const s of stories) {
|
|
65
73
|
const deps = (s.dependsOn || []).filter(d => known.has(d) && d !== s.number);
|
|
74
|
+
const fora = (s.dependsOn || []).filter(d => !known.has(d) && d !== s.number);
|
|
75
|
+
if (fora.length > 0) external.set(s.number, fora.sort((a, b) => a - b));
|
|
66
76
|
indegree.set(s.number, deps.length);
|
|
67
77
|
for (const d of deps) {
|
|
68
78
|
if (!dependents.has(d)) dependents.set(d, []);
|
|
@@ -88,7 +98,7 @@ export function orderStories(stories) {
|
|
|
88
98
|
.filter(([, deg]) => deg > 0)
|
|
89
99
|
.map(([n]) => n)
|
|
90
100
|
.sort((a, b) => a - b);
|
|
91
|
-
return { order, cycle };
|
|
101
|
+
return { order, cycle, external };
|
|
92
102
|
}
|
|
93
103
|
|
|
94
104
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spec-wave",
|
|
3
3
|
"displayName": "Spec Wave",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.23.0",
|
|
5
5
|
"description": "Fluxo spec-driven no GitHub (RFC-001): Projects v2, labels de gatilho, spec/plan gerados por Action, decomposição em duas etapas e implementação orientada a Stories/Tasks.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Astratech",
|
|
@@ -64,7 +64,7 @@ spec-wave:decompose-apply
|
|
|
64
64
|
```
|
|
65
65
|
Aplicar essa label **é** a aprovação humana — não há nova crítica.
|
|
66
66
|
|
|
67
|
-
5. Após a aplicação, as issues filhas aparecem como comentário na issue pai. Pai e filhas entram no board em **✅ Ready** (Status Todo; a Etapa nunca retrocede — itens já adiante não são tocados). As Stories trazem `Depende de: #N` — use a skill **order** para ver a ordem de execução.
|
|
67
|
+
5. Após a aplicação, as issues filhas aparecem como comentário na issue pai. Pai e filhas entram no board em **✅ Ready** (Status Todo; a Etapa nunca retrocede — itens já adiante não são tocados). As Stories trazem `Depende de: #N` — use a skill **order** para ver a ordem de execução. Se a issue pai tiver **milestone**, as filhas nascem nele (a entrega da Story pertence à release da Feature); sem milestone no pai, nascem sem.
|
|
68
68
|
|
|
69
69
|
6. A issue recebe `spec-wave:decomposed` (guard de idempotência): rodar de novo **não** duplica as issues.
|
|
70
70
|
|
|
@@ -93,7 +93,7 @@ Corpo técnico.
|
|
|
93
93
|
**Ao editar à mão:**
|
|
94
94
|
|
|
95
95
|
- a **posição** manda, não o número escrito — inserir uma Story no meio sem renumerar funciona
|
|
96
|
-
- `**Depende de:**`
|
|
96
|
+
- `**Depende de:**` aceita **irmãs** (`Story 1, Story 3`, 1-based, só para trás — apontar para si mesma ou para frente é **erro**, não filtro silencioso) e **issues de outras Features** (`#412`, que precisam JÁ existir); as duas formas convivem na mesma linha (`Story 1, #412`), e `—` significa nenhuma
|
|
97
97
|
- o corpo aceita markdown livre (`## Backend`, cercas de código) — só `## Story N` e `### Task N.M` são estrutura
|
|
98
98
|
- **para gerar outro rascunho do zero:** apague o arquivo e reaplique `spec-wave:decompose`
|
|
99
99
|
|
|
@@ -114,4 +114,4 @@ Depois **apague/feche as sub-issues antigas** (senão a detecção por sub-issue
|
|
|
114
114
|
|
|
115
115
|
## Dependências entre Stories
|
|
116
116
|
|
|
117
|
-
O `decompose` grava `Depende de: #N, #M` no corpo das Stories e cria a relação nativa *blocked by
|
|
117
|
+
O `decompose` grava `Depende de: #N, #M` no corpo das Stories e cria a relação nativa *blocked by* — para irmãs e para as issues de outras Features referenciadas com `#N` no rascunho (a issue precisa existir: o apply reprova o rascunho ANTES de criar qualquer coisa se não conseguir lê-la). Isso alimenta as skills **order** e **implement**. **Não apague essa linha** ao editar o corpo de uma Story; para mudar dependências, edite a linha (e/ou a relação *blocked by*).
|
|
@@ -11,12 +11,15 @@ allowed-tools:
|
|
|
11
11
|
Comando **local**:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npx @spec-wave/cli@latest order <feature>
|
|
14
|
+
npx @spec-wave/cli@latest order <feature> # uma Feature
|
|
15
|
+
npx @spec-wave/cli@latest order # o mapa de todas as Features com trabalho
|
|
15
16
|
```
|
|
16
17
|
|
|
17
18
|
| Arg | Descrição |
|
|
18
19
|
|-----|-----------|
|
|
19
|
-
| `<feature>` | Número da issue da **Feature**, ex.: `12` ou `#12`. Posicional,
|
|
20
|
+
| `<feature>` | Número da issue da **Feature**, ex.: `12` ou `#12`. Posicional, **opcional**. |
|
|
21
|
+
|
|
22
|
+
**Sem argumento**, o conjunto vem do **board** (não dos arquivos): todas as Features abertas fora de 🎉 Done, com as Stories de todas num **grafo só** e a Feature de cada uma ao lado. É o modo para responder "por onde os devs pegam agora" quando o trabalho está espalhado por várias Features — nesse escopo, dependência entre Features deixa de ser "externa" e entra na ordenação.
|
|
20
23
|
|
|
21
24
|
**Contexto:** leia `.spec-wave.json` (Read). Ausente → skill **setup**.
|
|
22
25
|
|
|
@@ -25,11 +28,12 @@ npx @spec-wave/cli@latest order <feature>
|
|
|
25
28
|
- As Stories da Feature em **ordem topológica** pelas dependências — a linha `Depende de: #N` no corpo **mesclada** com a relação nativa *blocked by* do GitHub
|
|
26
29
|
- A **Etapa atual** de cada Story no board
|
|
27
30
|
- Avisos de **ciclo de dependência** — essas Stories ficam **fora da ordem**; corrija as linhas `Depende de:`
|
|
28
|
-
- Avisos de **dependência fora de ordem** — Story já em 🚧 Desenvolvimento ou além dependendo de outra que não está em 🎉 Done
|
|
31
|
+
- Avisos de **dependência fora de ordem** — Story já em 🚧 Desenvolvimento ou além dependendo de outra que não está em 🎉 Done (no modo de uma Feature, também quando a bloqueadora é de **outra** Feature e continua aberta)
|
|
32
|
+
- **Bloqueadas por fora desta Feature** (modo de uma Feature) — dependências `#N` que não entram na ordenação porque a Story bloqueadora não está no conjunto, com o estado de cada uma. No modo sem argumento essa seção lista só o que ficou fora do board (Story concluída, Feature em Done, outro board)
|
|
29
33
|
|
|
30
34
|
## Passos
|
|
31
35
|
|
|
32
|
-
1. Rode o comando para a Feature.
|
|
36
|
+
1. Rode o comando para a Feature — ou **sem argumento** quando a pergunta for sobre a onda inteira, não sobre uma Feature.
|
|
33
37
|
2. Apresente a ordem ao usuário, marcando o que já está concluído e o que está pendente.
|
|
34
38
|
3. **Se houver ciclo**, isso é bloqueante para a skill **implement** no modo Feature (o comando aborta com exit 1). Ajude a quebrar o ciclo editando as linhas `Depende de:` nos corpos das Stories.
|
|
35
39
|
4. **Se houver dependência fora de ordem**, aponte o risco ao usuário antes de seguir.
|
|
@@ -24,7 +24,7 @@ Verifica se `spec.md` e `plan.md` contêm todas as seções obrigatórias e se n
|
|
|
24
24
|
|
|
25
25
|
3. Informe: "Validação iniciada. O workflow verifica se `spec.md` e `plan.md` contêm todas as seções obrigatórias."
|
|
26
26
|
|
|
27
|
-
4. **Se a validação falhar por conteúdo**, o workflow comenta os problemas na issue e
|
|
27
|
+
4. **Se a validação falhar por conteúdo**, o workflow comenta os problemas na issue e **não aplica nenhuma label de gatilho**. Oriente o usuário a corrigir e reaplicar `spec-wave:ready`. Quando o problema é só o título de uma seção, o comentário já diz qual título encontrou e qual esperava — renomear resolve. Só sugira `spec-wave:spec` se o documento precisar mesmo ser REGERADO: essa label **sobrescreve** o `spec.md`, inclusive o que foi revisado à mão.
|
|
28
28
|
|
|
29
29
|
5. **Se passar:** "Feature validada! Mova o card para **✅ Ready** e use a skill **decompose** para gerar o **rascunho** das Stories — nada é criado ainda."
|
|
30
30
|
|
|
@@ -227,7 +227,7 @@ Sem flags. Roda um checklist de diagnóstico no repositório atual: token GitHub
|
|
|
227
227
|
|----------|------|-----------|
|
|
228
228
|
| `<feature>` | string (obrigatório) | Número da issue da **Feature**, ex.: `12` ou `#12`. Argumento posicional. |
|
|
229
229
|
|
|
230
|
-
>
|
|
230
|
+
> **Sem argumento**, monta o mapa de TODAS as Features abertas fora de 🎉 Done num grafo só (conjunto vindo do board), com a Feature de cada Story ao lado — nesse escopo a dependência entre Features entra na ordenação. Com `<feature>`, lista as Stories da Feature em **ordem topológica** pelas dependências (linha `Depende de: #N` no corpo + relação nativa *blocked by*, mescladas), com a Etapa atual de cada uma no board. Dependências para **fora da Feature** não entram na ordenação (não há como saber onde a Story de outra Feature entra nesta sequência), mas aparecem em **"Bloqueadas por fora desta Feature"**, com o estado de cada bloqueadora. Avisa sobre **ciclos de dependência** (essas Stories ficam fora da ordem — corrija as linhas `Depende de`) e sobre **dependências fora de ordem** (Story já em Desenvolvimento+ dependendo de outra que não está Done). Use antes de escolher qual Story implementar.
|
|
231
231
|
|
|
232
232
|
### `@spec-wave/cli task <start|done> <n>` — transições de Task no board (comando LOCAL)
|
|
233
233
|
| Flag/Arg | Tipo | Descrição |
|
|
@@ -316,7 +316,7 @@ Corpo técnico.
|
|
|
316
316
|
|
|
317
317
|
**Ao editar à mão:**
|
|
318
318
|
- a **posição** manda, não o número escrito — inserir uma Story no meio sem renumerar funciona;
|
|
319
|
-
- `**Depende de:**`
|
|
319
|
+
- `**Depende de:**` aceita **irmãs** (`Story 1, Story 3`, 1-based, só para trás — apontar para si mesma ou para frente é **erro**, não filtro silencioso) e **issues de outras Features** (`#412`, que precisam JÁ existir); as duas formas convivem na mesma linha (`Story 1, #412`), e `—` significa nenhuma;
|
|
320
320
|
- o corpo aceita markdown livre (`## Backend`, cercas de código) — só `## Story N` e `### Task N.M` são estrutura;
|
|
321
321
|
- **para gerar outro rascunho do zero:** apague o arquivo e reaplique `spec-wave:decompose`.
|
|
322
322
|
|
|
@@ -606,7 +606,7 @@ Valida que spec.md e plan.md estão completos e a Feature pode avançar.
|
|
|
606
606
|
gh issue edit <número> --add-label "spec-wave:ready"
|
|
607
607
|
```
|
|
608
608
|
2. Informe: "Validação iniciada. O workflow verificará se spec.md e plan.md contêm todas as seções obrigatórias."
|
|
609
|
-
3. Se a validação falhar, o workflow comentará os problemas na issue e
|
|
609
|
+
3. Se a validação falhar, o workflow comentará os problemas na issue e **não aplicará label de gatilho nenhuma**. Informe o usuário para corrigir e reaplicar `spec-wave:ready`. Falha por título de seção vem com "encontrei X, esperava Y" — renomear resolve. `spec-wave:spec` só se o documento precisar ser REGERADO: ela **sobrescreve** o `spec.md` revisado.
|
|
610
610
|
4. **Se a issue tiver `spec-wave:critique-failed` ou `spec-wave:needs-human`**, a validação falha de imediato — são portões humanos: a crítica apontou contradições graves (comentário 🔎 na issue) ou esgotou as tentativas. Nesses casos a Feature **não** é devolvida para a etapa de spec; siga o fluxo da seção *Crítica adversarial* (corrigir a superfície certa → remover a label → re-aplicar `spec-wave:ready`).
|
|
611
611
|
5. Se passar, oriente: "Feature validada! Mova o card para **✅ Ready** e use `/spec-wave decompose <número>` para gerar o **rascunho** das Stories (nada é criado ainda)."
|
|
612
612
|
|
|
@@ -636,7 +636,7 @@ Para qualquer outro tipo (Spike, Bug, Story, Task, …) o Action **recusa** e co
|
|
|
636
636
|
gh issue edit <número> --add-label "spec-wave:decompose-apply"
|
|
637
637
|
```
|
|
638
638
|
Aplicar essa label **é** a aprovação humana — não há nova crítica.
|
|
639
|
-
5. Após a aplicação, as issues filhas aparecem como comentário na issue pai. 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 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.
|
|
639
|
+
5. Após a aplicação, as issues filhas aparecem como comentário na issue pai. 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 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. Se a issue pai tiver **milestone**, as filhas nascem nele; sem milestone no pai, nascem sem.
|
|
640
640
|
6. 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*.
|
|
641
641
|
|
|
642
642
|
> **Nunca pule a etapa 1** aplicando `spec-wave:decompose-apply` direto: sem `decomposition.md` o Action falha pedindo o rascunho.
|