@spec-wave/cli 0.18.1 → 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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "spec-wave",
3
3
  "displayName": "Spec Wave",
4
- "version": "0.18.1",
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.
@@ -1,5 +1,5 @@
1
1
  import { createLabel } from '../api/github-rest.mjs';
2
- import { ALL_LABELS } from '../config.mjs';
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
- export async function setupLabels(token, owner, repo, spinner) {
11
- for (let i = 0; i < ALL_LABELS.length; i++) {
12
- const label = ALL_LABELS[i];
13
- spinner.message(`Criando label ${i + 1}/${ALL_LABELS.length}: ${label.name}`);
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 < ALL_LABELS.length - 1) await sleep(DELAY_MS);
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
- code-review:
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-decompose-${{ github.event.issue.number }}
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-generate-bug-${{ github.event.issue.number }}
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-generate-plan-${{ github.event.issue.number }}
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-generate-spec-${{ github.event.issue.number }}
23
+ group: spec-wave-item-${{ github.event.issue.number }}
9
24
  cancel-in-progress: false
10
25
 
11
26
  jobs: