@spec-wave/cli 0.20.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.
Files changed (38) hide show
  1. package/bin/spec-wave.mjs +2 -2
  2. package/package.json +1 -1
  3. package/src/agent/anthropic-agent.mjs +3 -1
  4. package/src/agent/errors.mjs +59 -13
  5. package/src/agent/openrouter-agent.mjs +5 -1
  6. package/src/api/github-graphql.mjs +127 -7
  7. package/src/api/github-rest.mjs +9 -2
  8. package/src/commands/code-review.mjs +15 -4
  9. package/src/commands/decompose.mjs +172 -23
  10. package/src/commands/doctor.mjs +88 -8
  11. package/src/commands/move.mjs +58 -1
  12. package/src/commands/order.mjs +200 -7
  13. package/src/commands/qa.mjs +8 -2
  14. package/src/commands/repair-stage.mjs +6 -1
  15. package/src/commands/story.mjs +6 -1
  16. package/src/commands/task.mjs +6 -1
  17. package/src/commands/triage.mjs +4 -1
  18. package/src/commands/validate.mjs +39 -18
  19. package/src/config.mjs +47 -3
  20. package/src/lib/board.mjs +87 -3
  21. package/src/lib/bug-doc.mjs +71 -0
  22. package/src/lib/claude.mjs +23 -6
  23. package/src/lib/decomposition-doc.mjs +66 -14
  24. package/src/lib/dependencies.mjs +14 -4
  25. package/src/lib/implement-board.mjs +15 -11
  26. package/src/plugin/.claude-plugin/plugin.json +1 -1
  27. package/src/plugin/skills/decompose/SKILL.md +3 -3
  28. package/src/plugin/skills/order/SKILL.md +8 -4
  29. package/src/plugin/skills/ready/SKILL.md +1 -1
  30. package/src/templates/skill/SKILL.md +4 -4
  31. package/src/templates/workflows/code-review.yml +9 -2
  32. package/src/templates/workflows/critique.yml +19 -2
  33. package/src/templates/workflows/decompose.yml +19 -2
  34. package/src/templates/workflows/generate-bug.yml +19 -2
  35. package/src/templates/workflows/generate-plan.yml +19 -2
  36. package/src/templates/workflows/generate-spec.yml +19 -2
  37. package/src/templates/workflows/qa.yml +5 -1
  38. package/src/templates/workflows/validate.yml +19 -2
@@ -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
- > 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. 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.
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:**` usa referências **1-based** (`Story 1, Story 3`) ou `—`; apontar para si mesma ou para frente é **erro**, não filtro silencioso;
319
+ - `**Depende de:**` aceita **irmãs** (`Story 1, Story 3`, 1-based, 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 adicionará automaticamente `spec-wave:spec`. Informe o usuário para corrigir e tentar novamente.
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.
@@ -27,8 +27,12 @@ jobs:
27
27
  - uses: actions/setup-node@v4
28
28
  with:
29
29
  node-version: '24'
30
+
31
+ - name: Instala a CLI
32
+ run: npm install -g @spec-wave/cli@{{CLI_VERSION}}
33
+
30
34
  - id: resolve
31
- run: npx @spec-wave/cli@{{CLI_VERSION}} code-review --pr-number ${{ github.event.pull_request.number }} --resolve-only
35
+ run: spec-wave code-review --pr-number ${{ github.event.pull_request.number }} --resolve-only
32
36
  env:
33
37
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34
38
  GITHUB_REPOSITORY: ${{ github.repository }}
@@ -56,8 +60,11 @@ jobs:
56
60
  with:
57
61
  node-version: '24'
58
62
 
63
+ - name: Instala a CLI
64
+ run: npm install -g @spec-wave/cli@{{CLI_VERSION}}
65
+
59
66
  - name: Move Feature to Code Review
60
- run: npx @spec-wave/cli@{{CLI_VERSION}} code-review --pr-number ${{ github.event.pull_request.number }}
67
+ run: spec-wave code-review --pr-number ${{ github.event.pull_request.number }}
61
68
  env:
62
69
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
63
70
  PROJECT_TOKEN: ${{ secrets.GH_PROJECT_TOKEN }}
@@ -5,7 +5,21 @@ on:
5
5
  types: [labeled]
6
6
 
7
7
  concurrency:
8
- group: spec-wave-critique-${{ github.event.issue.number }}
8
+ # Um run que NÃO vai fazer nada não pode disputar este grupo.
9
+ #
10
+ # `issues: [labeled]` dispara para QUALQUER label — prioridade, tipo,
11
+ # `spec-wave:model:*`. O job é filtrado pelo `if:` e termina skipped, mas o RUN
12
+ # entra na fila de concurrency do mesmo jeito, e com `cancel-in-progress: false`
13
+ # o GitHub cancela o run que já estava PENDENTE para pôr o novo no lugar. Foi
14
+ # assim que um decompose enfileirado atrás de um generate-plan morreu cancelado
15
+ # em 1 segundo por causa de uma label sem relação nenhuma — a label de gatilho
16
+ # fica na issue, `labeled` não redispara com ela aplicada, e o fluxo trava sem
17
+ # nenhum sinal. Rotular o run inútil com um grupo único (`github.run_id`) o tira
18
+ # da fila sem afrouxar a serialização de quem realmente vai rodar.
19
+ group: >-
20
+ ${{ github.event.label.name == 'spec-wave:critique'
21
+ && format('spec-wave-critique-{0}', github.event.issue.number)
22
+ || format('spec-wave-noop-{0}', github.run_id) }}
9
23
  cancel-in-progress: false
10
24
 
11
25
  jobs:
@@ -29,8 +43,11 @@ jobs:
29
43
  with:
30
44
  node-version: '24'
31
45
 
46
+ - name: Instala a CLI
47
+ run: npm install -g @spec-wave/cli@{{CLI_VERSION}}
48
+
32
49
  - name: Critique plan.md as-is
33
- run: npx @spec-wave/cli@{{CLI_VERSION}} critique --issue-number ${{ github.event.issue.number }}
50
+ run: spec-wave critique --issue-number ${{ github.event.issue.number }}
34
51
  env:
35
52
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
36
53
  ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
@@ -20,7 +20,21 @@ on:
20
20
  # o spec.md/plan.md do checkout — que uma geração concorrente estaria trocando
21
21
  # debaixo dele.
22
22
  concurrency:
23
- group: spec-wave-item-${{ github.event.issue.number }}
23
+ # Um run que NÃO vai fazer nada não pode disputar este grupo.
24
+ #
25
+ # `issues: [labeled]` dispara para QUALQUER label — prioridade, tipo,
26
+ # `spec-wave:model:*`. O job é filtrado pelo `if:` e termina skipped, mas o RUN
27
+ # entra na fila de concurrency do mesmo jeito, e com `cancel-in-progress: false`
28
+ # o GitHub cancela o run que já estava PENDENTE para pôr o novo no lugar. Foi
29
+ # assim que um decompose enfileirado atrás de um generate-plan morreu cancelado
30
+ # em 1 segundo por causa de uma label sem relação nenhuma — a label de gatilho
31
+ # fica na issue, `labeled` não redispara com ela aplicada, e o fluxo trava sem
32
+ # nenhum sinal. Rotular o run inútil com um grupo único (`github.run_id`) o tira
33
+ # da fila sem afrouxar a serialização de quem realmente vai rodar.
34
+ group: >-
35
+ ${{ (github.event.label.name == 'spec-wave:decompose' || github.event.label.name == 'spec-wave:decompose-apply')
36
+ && format('spec-wave-item-{0}', github.event.issue.number)
37
+ || format('spec-wave-noop-{0}', github.run_id) }}
24
38
  cancel-in-progress: false
25
39
 
26
40
  jobs:
@@ -50,9 +64,12 @@ jobs:
50
64
  with:
51
65
  node-version: '24'
52
66
 
67
+ - name: Instala a CLI
68
+ run: npm install -g @spec-wave/cli@{{CLI_VERSION}}
69
+
53
70
  - name: Decompose into Stories and Tasks
54
71
  run: >
55
- npx @spec-wave/cli@{{CLI_VERSION}} decompose
72
+ spec-wave decompose
56
73
  --issue-number ${{ github.event.issue.number }}
57
74
  ${{ github.event.label.name == 'spec-wave:decompose-apply' && '--apply' || '' }}
58
75
  env:
@@ -12,7 +12,21 @@ on:
12
12
  # diferentes a disputa é resolvida no laço de repetição de push do
13
13
  # lib/flow-run.mjs — serializar o repositório inteiro mataria a vazão.
14
14
  concurrency:
15
- group: spec-wave-item-${{ github.event.issue.number }}
15
+ # Um run que NÃO vai fazer nada não pode disputar este grupo.
16
+ #
17
+ # `issues: [labeled]` dispara para QUALQUER label — prioridade, tipo,
18
+ # `spec-wave:model:*`. O job é filtrado pelo `if:` e termina skipped, mas o RUN
19
+ # entra na fila de concurrency do mesmo jeito, e com `cancel-in-progress: false`
20
+ # o GitHub cancela o run que já estava PENDENTE para pôr o novo no lugar. Foi
21
+ # assim que um decompose enfileirado atrás de um generate-plan morreu cancelado
22
+ # em 1 segundo por causa de uma label sem relação nenhuma — a label de gatilho
23
+ # fica na issue, `labeled` não redispara com ela aplicada, e o fluxo trava sem
24
+ # nenhum sinal. Rotular o run inútil com um grupo único (`github.run_id`) o tira
25
+ # da fila sem afrouxar a serialização de quem realmente vai rodar.
26
+ group: >-
27
+ ${{ github.event.label.name == 'spec-wave:bug'
28
+ && format('spec-wave-item-{0}', github.event.issue.number)
29
+ || format('spec-wave-noop-{0}', github.run_id) }}
16
30
  cancel-in-progress: false
17
31
 
18
32
  jobs:
@@ -34,8 +48,11 @@ jobs:
34
48
  with:
35
49
  node-version: '24'
36
50
 
51
+ - name: Instala a CLI
52
+ run: npm install -g @spec-wave/cli@{{CLI_VERSION}}
53
+
37
54
  - name: Generate bug.md
38
- run: npx @spec-wave/cli@{{CLI_VERSION}} generate-bug --issue-number ${{ github.event.issue.number }}
55
+ run: spec-wave generate-bug --issue-number ${{ github.event.issue.number }}
39
56
  env:
40
57
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41
58
  ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
@@ -20,7 +20,21 @@ on:
20
20
  # mataria a vazão, e a disputa entre issues diferentes é resolvida onde ela
21
21
  # acontece — no laço de repetição de push do lib/flow-run.mjs.
22
22
  concurrency:
23
- group: spec-wave-item-${{ github.event.issue.number }}
23
+ # Um run que NÃO vai fazer nada não pode disputar este grupo.
24
+ #
25
+ # `issues: [labeled]` dispara para QUALQUER label — prioridade, tipo,
26
+ # `spec-wave:model:*`. O job é filtrado pelo `if:` e termina skipped, mas o RUN
27
+ # entra na fila de concurrency do mesmo jeito, e com `cancel-in-progress: false`
28
+ # o GitHub cancela o run que já estava PENDENTE para pôr o novo no lugar. Foi
29
+ # assim que um decompose enfileirado atrás de um generate-plan morreu cancelado
30
+ # em 1 segundo por causa de uma label sem relação nenhuma — a label de gatilho
31
+ # fica na issue, `labeled` não redispara com ela aplicada, e o fluxo trava sem
32
+ # nenhum sinal. Rotular o run inútil com um grupo único (`github.run_id`) o tira
33
+ # da fila sem afrouxar a serialização de quem realmente vai rodar.
34
+ group: >-
35
+ ${{ github.event.label.name == 'spec-wave:plan'
36
+ && format('spec-wave-item-{0}', github.event.issue.number)
37
+ || format('spec-wave-noop-{0}', github.run_id) }}
24
38
  cancel-in-progress: false
25
39
 
26
40
  jobs:
@@ -42,8 +56,11 @@ jobs:
42
56
  with:
43
57
  node-version: '24'
44
58
 
59
+ - name: Instala a CLI
60
+ run: npm install -g @spec-wave/cli@{{CLI_VERSION}}
61
+
45
62
  - name: Generate plan.md
46
- run: npx @spec-wave/cli@{{CLI_VERSION}} generate-plan --issue-number ${{ github.event.issue.number }}
63
+ run: spec-wave generate-plan --issue-number ${{ github.event.issue.number }}
47
64
  env:
48
65
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
49
66
  ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
@@ -20,7 +20,21 @@ on:
20
20
  # mataria a vazão, e a disputa entre issues diferentes é resolvida onde ela
21
21
  # acontece — no laço de repetição de push do lib/flow-run.mjs.
22
22
  concurrency:
23
- group: spec-wave-item-${{ github.event.issue.number }}
23
+ # Um run que NÃO vai fazer nada não pode disputar este grupo.
24
+ #
25
+ # `issues: [labeled]` dispara para QUALQUER label — prioridade, tipo,
26
+ # `spec-wave:model:*`. O job é filtrado pelo `if:` e termina skipped, mas o RUN
27
+ # entra na fila de concurrency do mesmo jeito, e com `cancel-in-progress: false`
28
+ # o GitHub cancela o run que já estava PENDENTE para pôr o novo no lugar. Foi
29
+ # assim que um decompose enfileirado atrás de um generate-plan morreu cancelado
30
+ # em 1 segundo por causa de uma label sem relação nenhuma — a label de gatilho
31
+ # fica na issue, `labeled` não redispara com ela aplicada, e o fluxo trava sem
32
+ # nenhum sinal. Rotular o run inútil com um grupo único (`github.run_id`) o tira
33
+ # da fila sem afrouxar a serialização de quem realmente vai rodar.
34
+ group: >-
35
+ ${{ github.event.label.name == 'spec-wave:spec'
36
+ && format('spec-wave-item-{0}', github.event.issue.number)
37
+ || format('spec-wave-noop-{0}', github.run_id) }}
24
38
  cancel-in-progress: false
25
39
 
26
40
  jobs:
@@ -42,8 +56,11 @@ jobs:
42
56
  with:
43
57
  node-version: '24'
44
58
 
59
+ - name: Instala a CLI
60
+ run: npm install -g @spec-wave/cli@{{CLI_VERSION}}
61
+
45
62
  - name: Generate spec.md
46
- run: npx @spec-wave/cli@{{CLI_VERSION}} generate-spec --issue-number ${{ github.event.issue.number }}
63
+ run: spec-wave generate-spec --issue-number ${{ github.event.issue.number }}
47
64
  env:
48
65
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
49
66
  ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
@@ -24,8 +24,12 @@ jobs:
24
24
  with:
25
25
  node-version: '24'
26
26
 
27
+ - name: Instala a CLI
28
+ run: npm install -g @spec-wave/cli@{{CLI_VERSION}}
29
+
30
+
27
31
  - name: Move Feature to QA
28
- run: npx @spec-wave/cli@{{CLI_VERSION}} qa --pr-number ${{ github.event.pull_request.number }}
32
+ run: spec-wave qa --pr-number ${{ github.event.pull_request.number }}
29
33
  env:
30
34
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31
35
  PROJECT_TOKEN: ${{ secrets.GH_PROJECT_TOKEN }}
@@ -5,7 +5,21 @@ on:
5
5
  types: [labeled]
6
6
 
7
7
  concurrency:
8
- group: spec-wave-validate-${{ github.event.issue.number }}
8
+ # Um run que NÃO vai fazer nada não pode disputar este grupo.
9
+ #
10
+ # `issues: [labeled]` dispara para QUALQUER label — prioridade, tipo,
11
+ # `spec-wave:model:*`. O job é filtrado pelo `if:` e termina skipped, mas o RUN
12
+ # entra na fila de concurrency do mesmo jeito, e com `cancel-in-progress: false`
13
+ # o GitHub cancela o run que já estava PENDENTE para pôr o novo no lugar. Foi
14
+ # assim que um decompose enfileirado atrás de um generate-plan morreu cancelado
15
+ # em 1 segundo por causa de uma label sem relação nenhuma — a label de gatilho
16
+ # fica na issue, `labeled` não redispara com ela aplicada, e o fluxo trava sem
17
+ # nenhum sinal. Rotular o run inútil com um grupo único (`github.run_id`) o tira
18
+ # da fila sem afrouxar a serialização de quem realmente vai rodar.
19
+ group: >-
20
+ ${{ github.event.label.name == 'spec-wave:ready'
21
+ && format('spec-wave-validate-{0}', github.event.issue.number)
22
+ || format('spec-wave-noop-{0}', github.run_id) }}
9
23
  cancel-in-progress: false
10
24
 
11
25
  jobs:
@@ -26,8 +40,11 @@ jobs:
26
40
  with:
27
41
  node-version: '24'
28
42
 
43
+ - name: Instala a CLI
44
+ run: npm install -g @spec-wave/cli@{{CLI_VERSION}}
45
+
29
46
  - name: Validate spec and plan
30
- run: npx @spec-wave/cli@{{CLI_VERSION}} validate --issue-number ${{ github.event.issue.number }}
47
+ run: spec-wave validate --issue-number ${{ github.event.issue.number }}
31
48
  env:
32
49
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33
50
  GITHUB_REPOSITORY: ${{ github.repository }}