@spec-wave/cli 0.18.0 → 0.19.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 +34 -3
- package/package.json +1 -1
- package/src/agent/anthropic-agent.mjs +10 -1
- package/src/agent/errors.mjs +57 -3
- package/src/agent/openrouter-agent.mjs +12 -2
- package/src/api/auth.mjs +217 -9
- package/src/api/github-graphql.mjs +33 -0
- package/src/commands/code-review.mjs +186 -31
- package/src/commands/decompose.mjs +151 -13
- package/src/commands/doctor.mjs +195 -35
- package/src/commands/generate-bug.mjs +18 -15
- package/src/commands/generate-plan.mjs +94 -6
- package/src/commands/generate-spec.mjs +7 -4
- package/src/commands/repair-stage.mjs +232 -0
- package/src/commands/update.mjs +13 -5
- package/src/config.mjs +43 -0
- package/src/lib/board.mjs +44 -0
- package/src/lib/claude.mjs +59 -9
- package/src/lib/critique.mjs +186 -14
- package/src/lib/decomposition-doc.mjs +59 -7
- package/src/lib/flow-run.mjs +134 -5
- package/src/lib/prompt-loader.mjs +30 -3
- package/src/plugin/.claude-plugin/plugin.json +1 -1
- package/src/plugin/skills/move/SKILL.md +29 -0
- package/src/plugin/skills/plan/SKILL.md +13 -0
- package/src/plugin/skills/spec/model-prompt.critique.md +58 -0
- package/src/setup/labels.mjs +10 -6
- package/src/templates/workflows/code-review.yml +34 -1
- package/src/templates/workflows/decompose.yml +12 -1
- package/src/templates/workflows/generate-bug.yml +8 -1
- package/src/templates/workflows/generate-plan.yml +16 -1
- package/src/templates/workflows/generate-spec.yml +16 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spec-wave",
|
|
3
3
|
"displayName": "Spec Wave",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.19.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",
|
|
@@ -43,6 +43,34 @@ npx @spec-wave/cli@latest move <n> <etapa> [--status <valor>]
|
|
|
43
43
|
3. Se o comando recusar por Etapa já adiante, apenas informe a Etapa atual — não tente contornar via `gh api graphql`.
|
|
44
44
|
4. Se recusar por ambiguidade, escolha entre as candidatas listadas e rode de novo.
|
|
45
45
|
|
|
46
|
+
## Quando a automação errou: `repair-stage`
|
|
47
|
+
|
|
48
|
+
`move` não retrocede — e isso vale inclusive quando quem errou foi a automação
|
|
49
|
+
(uma corrida entre workflows já pôs Stories em `🎉 Done` no instante em que
|
|
50
|
+
nasceram). Para esse caso existe um comando **separado**:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx @spec-wave/cli@latest repair-stage <issues> <etapa> --yes --reason "<motivo>"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
| Flag/Arg | Descrição |
|
|
57
|
+
|----------|-----------|
|
|
58
|
+
| `<issues>` | **Obrigatório.** Uma ou várias: `529` ou `529,530,531`. |
|
|
59
|
+
| `<etapa>` | **Obrigatório.** A Etapa **correta** — pode ser anterior à atual. |
|
|
60
|
+
| `--reason` | **Obrigatório.** Vai para o comentário de auditoria na issue. |
|
|
61
|
+
| `--yes` | **Obrigatório.** Confirma o reparo. |
|
|
62
|
+
| `--status` | Também corrige o Status. Default: não mexe. |
|
|
63
|
+
| `--dry-run` | Mostra o que seria reparado, sem alterar nada. |
|
|
64
|
+
|
|
65
|
+
**Nunca use este comando como atalho para o fluxo normal.** Ele existe para
|
|
66
|
+
desfazer erro de automação, e cada uso deixa um comentário público na issue
|
|
67
|
+
dizendo quem reparou, de onde para onde e por quê. Se o usuário pedir para
|
|
68
|
+
"voltar o card", pergunte **por que** antes: se a resposta for "mudou o escopo"
|
|
69
|
+
ou "o time decidiu revisar de novo", isso é fluxo, não reparo — e a resposta
|
|
70
|
+
certa é conversar sobre a etapa correta, não retroceder o board.
|
|
71
|
+
|
|
72
|
+
Antes de reparar, rode com `--dry-run` e mostre o resultado ao usuário.
|
|
73
|
+
|
|
46
74
|
## Quando preferir outra skill
|
|
47
75
|
|
|
48
76
|
| Caso | Skill |
|
|
@@ -50,3 +78,4 @@ npx @spec-wave/cli@latest move <n> <etapa> [--status <valor>]
|
|
|
50
78
|
| Task iniciando ou concluindo | **task** (`start` / `done`) |
|
|
51
79
|
| Story indo para Code Review | **story** (`review`) |
|
|
52
80
|
| Feature avançando após todas as Stories em review, ou indo para QA/Homologação/Deploy | **move** (esta) |
|
|
81
|
+
| Desfazer Etapa errada gravada pela automação | **move** (esta), via `repair-stage` |
|
|
@@ -56,3 +56,16 @@ system_info:
|
|
|
56
56
|
## Se falhar
|
|
57
57
|
|
|
58
58
|
Depois do `generate-plan` roda a **crítica adversarial**, que vira um comentário 🔎 na issue. Se ela apontar findings **graves**, a issue recebe `spec-wave:critique-failed` — corrija o **`plan.md`** (ou a `spec.md` que o embasa), commite, remova a label e reaplique `spec-wave:ready`. Detalhes na skill **workflow**.
|
|
59
|
+
|
|
60
|
+
## Criticar sem regerar
|
|
61
|
+
|
|
62
|
+
Corrigir o documento à mão **não é desvio** — é o que o fluxo pede quando a crítica reprova. Para rodar a crítica de novo sobre o texto corrigido, **nunca** reaplique `spec-wave:plan`: ele regenera o `plan.md` do zero e descarta a correção.
|
|
63
|
+
|
|
64
|
+
| Situação | Comando |
|
|
65
|
+
|----------|---------|
|
|
66
|
+
| Quero a crítica oficial de novo, na issue | label `spec-wave:critique` (ou `critique --issue-number <n>`) |
|
|
67
|
+
| Quero só saber como está, enquanto edito | `npx @spec-wave/cli@latest critique --file docs/features/<slug>/plan.md` |
|
|
68
|
+
|
|
69
|
+
O `--file` **não** comenta na issue, **não** aplica label e **não** consome tentativa da crítica — é consulta, não portão. Aceita `spec.md`, `plan.md`, `decomposition.md` e `bug.md` (o tipo sai do nome do arquivo; use `--kind` para forçar). Com `--fail-on-grave` ele sai com código 1, para usar em script.
|
|
70
|
+
|
|
71
|
+
Um finding **grave** só bloqueia se vier com citação literal do trecho e não se auto-refutar; os que não passam nesse teste aparecem como **↘️ Rebaixados** no comentário, com o motivo — visíveis, mas sem travar o fluxo.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: critique-spec
|
|
3
|
+
action: critique
|
|
4
|
+
description: Critério da crítica adversarial da spec.md contra a issue de origem e as regras de negócio declaradas.
|
|
5
|
+
tools: [Read, Glob, Grep]
|
|
6
|
+
maxTurns: 20
|
|
7
|
+
verifiers: 3
|
|
8
|
+
lenses:
|
|
9
|
+
- "completude — algum comportamento pedido na issue não tem regra, fluxo nem critério de aceite?"
|
|
10
|
+
- "contradição interna — duas partes da spec decidem coisas incompatíveis sobre o mesmo caso?"
|
|
11
|
+
- "decisão fantasma — a spec afirma como algo funciona onde na verdade existe uma pergunta em aberto?"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Crítica adversarial do spec.md
|
|
15
|
+
|
|
16
|
+
Você audita a `spec.md` — a especificação FUNCIONAL, escrita antes do plano técnico. Seu papel é
|
|
17
|
+
encontrar problemas, não elogiar.
|
|
18
|
+
|
|
19
|
+
Diferente da crítica do plano, aqui não há um documento "de cima" contra o qual conferir linha a
|
|
20
|
+
linha: a referência é a issue de origem, as regras de negócio citadas e a coerência da spec consigo
|
|
21
|
+
mesma.
|
|
22
|
+
|
|
23
|
+
## O que caracteriza um achado
|
|
24
|
+
|
|
25
|
+
- **Requisito da issue sem cobertura** — comportamento pedido que não virou regra, fluxo nem
|
|
26
|
+
critério de aceite.
|
|
27
|
+
- **Contradição interna** — duas seções decidindo diferente sobre o mesmo caso (o fluxo alternativo
|
|
28
|
+
faz X, o critério de aceite exige Y).
|
|
29
|
+
- **Critério de aceite não verificável** — Gherkin sem dado observável, ou que depende de algo que a
|
|
30
|
+
spec não define.
|
|
31
|
+
- **Decisão fantasma** — a spec afirma um comportamento onde havia uma pergunta em aberto. Se um
|
|
32
|
+
`[TODO: …]` foi respondido no texto sem que a decisão tenha sido tomada por quem podia tomá-la,
|
|
33
|
+
isso é achado: uma decisão inventada é pior que uma pendência visível.
|
|
34
|
+
- **Persona ou regra órfã** — texto que não pertence a esta Feature (resíduo de outro documento).
|
|
35
|
+
Vale conferir se a spec fala de sistemas, telas ou perfis que a issue não menciona.
|
|
36
|
+
- **Regra de negócio contradita** — a spec inverte uma restrição declarada (retenção de dados,
|
|
37
|
+
consentimento, limites, LGPD).
|
|
38
|
+
|
|
39
|
+
<!-- requires-tools -->
|
|
40
|
+
## Como verificar
|
|
41
|
+
|
|
42
|
+
Você tem `Read`, `Glob` e `Grep`. A spec descreve comportamento, não código — mas quando ela afirma
|
|
43
|
+
que "hoje o sistema faz X", isso é verificável: abra o repositório e confira. Afirmação sobre o
|
|
44
|
+
estado atual que não se sustenta é achado.
|
|
45
|
+
<!-- /requires-tools -->
|
|
46
|
+
## Barra de rigor
|
|
47
|
+
|
|
48
|
+
NÃO invente problemas. Uma spec pode legitimamente deixar pendências marcadas — `[TODO: …]`
|
|
49
|
+
explícito é decisão adiada, não defeito. O defeito é a pendência que sumiu sem ter sido decidida.
|
|
50
|
+
|
|
51
|
+
Ambiguidade que um implementador resolveria de duas formas diferentes é achado. Ambiguidade que
|
|
52
|
+
qualquer leitor resolveria igual não é.
|
|
53
|
+
|
|
54
|
+
## Consequência
|
|
55
|
+
|
|
56
|
+
Um achado marcado como **grave** bloqueia o avanço até correção; um **menor** é reportado sem
|
|
57
|
+
bloquear. Um achado grave custa o tempo de um humano — reserve-o para o que faria a implementação
|
|
58
|
+
sair errada, não para preferência de redação.
|
package/src/setup/labels.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createLabel } from '../api/github-rest.mjs';
|
|
2
|
-
import {
|
|
2
|
+
import { allLabelsFor } from '../config.mjs';
|
|
3
3
|
|
|
4
4
|
const DELAY_MS = 120;
|
|
5
5
|
|
|
@@ -7,11 +7,15 @@ function sleep(ms) {
|
|
|
7
7
|
return new Promise(r => setTimeout(r, ms));
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
// `fileAi` traz as labels de modelo derivadas de `ai.modelAliases`. No `init` ele
|
|
11
|
+
// vem vazio (o config ainda nem foi gravado) e o conjunto é só o do fluxo; quem
|
|
12
|
+
// cria as de modelo depois é o `update`, que já lê o config do repo.
|
|
13
|
+
export async function setupLabels(token, owner, repo, spinner, fileAi) {
|
|
14
|
+
const labels = allLabelsFor(fileAi);
|
|
15
|
+
for (let i = 0; i < labels.length; i++) {
|
|
16
|
+
const label = labels[i];
|
|
17
|
+
spinner.message(`Criando label ${i + 1}/${labels.length}: ${label.name}`);
|
|
14
18
|
await createLabel(token, owner, repo, label);
|
|
15
|
-
if (i <
|
|
19
|
+
if (i < labels.length - 1) await sleep(DELAY_MS);
|
|
16
20
|
}
|
|
17
21
|
}
|
|
@@ -4,13 +4,46 @@ on:
|
|
|
4
4
|
pull_request:
|
|
5
5
|
types: [opened, reopened]
|
|
6
6
|
|
|
7
|
+
# Trava por PR: protege contra o mesmo PR ser processado duas vezes. A trava que
|
|
8
|
+
# importa de verdade é a do job `move`, por ITEM — ver o comentário lá.
|
|
7
9
|
concurrency:
|
|
8
10
|
group: spec-wave-code-review-${{ github.event.pull_request.number }}
|
|
9
11
|
cancel-in-progress: false
|
|
10
12
|
|
|
11
13
|
jobs:
|
|
12
|
-
|
|
14
|
+
# Descobre a Feature-alvo ANTES de tocar no board. Existe só para dar a chave
|
|
15
|
+
# de concurrency ao job seguinte: `concurrency` de workflow é avaliada no
|
|
16
|
+
# trigger, quando ainda não se sabe qual issue o PR implementa.
|
|
17
|
+
resolve:
|
|
13
18
|
runs-on: ubuntu-latest
|
|
19
|
+
permissions:
|
|
20
|
+
issues: read
|
|
21
|
+
pull-requests: read
|
|
22
|
+
contents: read
|
|
23
|
+
outputs:
|
|
24
|
+
feature: ${{ steps.resolve.outputs.feature }}
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
- uses: actions/setup-node@v4
|
|
28
|
+
with:
|
|
29
|
+
node-version: '24'
|
|
30
|
+
- id: resolve
|
|
31
|
+
run: npx @spec-wave/cli@{{CLI_VERSION}} code-review --pr-number ${{ github.event.pull_request.number }} --resolve-only
|
|
32
|
+
env:
|
|
33
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
34
|
+
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
35
|
+
|
|
36
|
+
move:
|
|
37
|
+
needs: resolve
|
|
38
|
+
# PR sem vínculo explícito não resolve Feature nenhuma — e não move nada.
|
|
39
|
+
if: needs.resolve.outputs.feature != ''
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
# MESMO namespace do decompose.yml: um `decompose-apply` em curso na Feature
|
|
42
|
+
# segura este job na fila em vez de disputar a subárvore com ele. Foi essa
|
|
43
|
+
# corrida que fez duas Stories e duas Tasks nascerem concluídas.
|
|
44
|
+
concurrency:
|
|
45
|
+
group: spec-wave-item-${{ needs.resolve.outputs.feature }}
|
|
46
|
+
cancel-in-progress: false
|
|
14
47
|
permissions:
|
|
15
48
|
issues: write
|
|
16
49
|
pull-requests: write
|
|
@@ -8,8 +8,19 @@ on:
|
|
|
8
8
|
# concurrency serializa runs da mesma issue — inclusive um `decompose-apply`
|
|
9
9
|
# enfileirado atrás de um `decompose` — e o guard de idempotência
|
|
10
10
|
# (label spec-wave:decomposed) descarta o run enfileirado.
|
|
11
|
+
#
|
|
12
|
+
# O namespace `spec-wave-item-<issue>` é COMPARTILHADO com o job `move` do
|
|
13
|
+
# code-review.yml: os dois mexem na mesma subárvore, e rodar em paralelo foi o
|
|
14
|
+
# que corrompeu a Etapa de sete itens (Stories/Tasks criadas no meio do run do
|
|
15
|
+
# code-review nasceram em Done). Renomear este grupo sem renomear lá desfaz a
|
|
16
|
+
# proteção.
|
|
17
|
+
#
|
|
18
|
+
# generate-spec, generate-plan e generate-bug usam o MESMO namespace por outro
|
|
19
|
+
# motivo: os quatro commitam e empurram na branch default, e este aqui ainda lê
|
|
20
|
+
# o spec.md/plan.md do checkout — que uma geração concorrente estaria trocando
|
|
21
|
+
# debaixo dele.
|
|
11
22
|
concurrency:
|
|
12
|
-
group: spec-wave-
|
|
23
|
+
group: spec-wave-item-${{ github.event.issue.number }}
|
|
13
24
|
cancel-in-progress: false
|
|
14
25
|
|
|
15
26
|
jobs:
|
|
@@ -4,8 +4,15 @@ on:
|
|
|
4
4
|
issues:
|
|
5
5
|
types: [labeled]
|
|
6
6
|
|
|
7
|
+
# Trava por ITEM, no mesmo namespace de generate-spec/plan/decompose e do job
|
|
8
|
+
# `move` do code-review.yml. Este job GRAVA no repositório (commit + pull
|
|
9
|
+
# --rebase + push na branch default) e escreve o relatório de uso no comentário
|
|
10
|
+
# da issue com leitura-modificação-escrita; a trava impede que outro job do
|
|
11
|
+
# spec-wave na MESMA issue faça as duas coisas ao mesmo tempo. Entre issues
|
|
12
|
+
# diferentes a disputa é resolvida no laço de repetição de push do
|
|
13
|
+
# lib/flow-run.mjs — serializar o repositório inteiro mataria a vazão.
|
|
7
14
|
concurrency:
|
|
8
|
-
group: spec-wave-
|
|
15
|
+
group: spec-wave-item-${{ github.event.issue.number }}
|
|
9
16
|
cancel-in-progress: false
|
|
10
17
|
|
|
11
18
|
jobs:
|
|
@@ -4,8 +4,23 @@ on:
|
|
|
4
4
|
issues:
|
|
5
5
|
types: [labeled]
|
|
6
6
|
|
|
7
|
+
# Trava por ITEM, no mesmo namespace do decompose.yml e do job `move` do
|
|
8
|
+
# code-review.yml. Duas razões:
|
|
9
|
+
#
|
|
10
|
+
# 1. spec, plan e decompose GRAVAM no repositório (commit + pull --rebase +
|
|
11
|
+
# push na branch default). Em namespaces separados, aplicar `spec-wave:spec`
|
|
12
|
+
# e `spec-wave:plan` juntos na mesma Feature põe os dois para rodar em
|
|
13
|
+
# paralelo — e o plan lê o spec.md do checkout, então pode gerar o plano
|
|
14
|
+
# sem a spec, em silêncio (generate-plan.mjs:160-169).
|
|
15
|
+
# 2. Os dois escrevem o relatório de uso no MESMO comentário da issue, com
|
|
16
|
+
# leitura-modificação-escrita (usage-report.mjs:152-160). Em paralelo, um
|
|
17
|
+
# sobrescreve o custo registrado pelo outro.
|
|
18
|
+
#
|
|
19
|
+
# Continua sendo por issue de propósito: serializar o repositório inteiro
|
|
20
|
+
# mataria a vazão, e a disputa entre issues diferentes é resolvida onde ela
|
|
21
|
+
# acontece — no laço de repetição de push do lib/flow-run.mjs.
|
|
7
22
|
concurrency:
|
|
8
|
-
group: spec-wave-
|
|
23
|
+
group: spec-wave-item-${{ github.event.issue.number }}
|
|
9
24
|
cancel-in-progress: false
|
|
10
25
|
|
|
11
26
|
jobs:
|
|
@@ -4,8 +4,23 @@ on:
|
|
|
4
4
|
issues:
|
|
5
5
|
types: [labeled]
|
|
6
6
|
|
|
7
|
+
# Trava por ITEM, no mesmo namespace do decompose.yml e do job `move` do
|
|
8
|
+
# code-review.yml. Duas razões:
|
|
9
|
+
#
|
|
10
|
+
# 1. spec, plan e decompose GRAVAM no repositório (commit + pull --rebase +
|
|
11
|
+
# push na branch default). Em namespaces separados, aplicar `spec-wave:spec`
|
|
12
|
+
# e `spec-wave:plan` juntos na mesma Feature põe os dois para rodar em
|
|
13
|
+
# paralelo — e o plan lê o spec.md do checkout, então pode gerar o plano
|
|
14
|
+
# sem a spec, em silêncio (generate-plan.mjs:160-169).
|
|
15
|
+
# 2. Os dois escrevem o relatório de uso no MESMO comentário da issue, com
|
|
16
|
+
# leitura-modificação-escrita (usage-report.mjs:152-160). Em paralelo, um
|
|
17
|
+
# sobrescreve o custo registrado pelo outro.
|
|
18
|
+
#
|
|
19
|
+
# Continua sendo por issue de propósito: serializar o repositório inteiro
|
|
20
|
+
# mataria a vazão, e a disputa entre issues diferentes é resolvida onde ela
|
|
21
|
+
# acontece — no laço de repetição de push do lib/flow-run.mjs.
|
|
7
22
|
concurrency:
|
|
8
|
-
group: spec-wave-
|
|
23
|
+
group: spec-wave-item-${{ github.event.issue.number }}
|
|
9
24
|
cancel-in-progress: false
|
|
10
25
|
|
|
11
26
|
jobs:
|