@spec-wave/cli 0.12.0 → 0.14.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 (40) hide show
  1. package/README.md +39 -27
  2. package/bin/spec-wave.mjs +14 -4
  3. package/package.json +1 -1
  4. package/src/api/github-graphql.mjs +0 -4
  5. package/src/api/github-rest.mjs +0 -13
  6. package/src/commands/code-review.mjs +5 -8
  7. package/src/commands/decompose.mjs +410 -251
  8. package/src/commands/dev-agent.mjs +3 -2
  9. package/src/commands/doctor.mjs +239 -9
  10. package/src/commands/generate-plan.mjs +111 -51
  11. package/src/commands/generate-spec.mjs +20 -22
  12. package/src/commands/implement.mjs +46 -24
  13. package/src/commands/info.mjs +4 -3
  14. package/src/commands/issue.mjs +4 -4
  15. package/src/commands/move.mjs +162 -0
  16. package/src/commands/order.mjs +1 -12
  17. package/src/commands/qa.mjs +5 -8
  18. package/src/commands/refresh.mjs +4 -3
  19. package/src/commands/story.mjs +1 -12
  20. package/src/commands/task.mjs +1 -11
  21. package/src/commands/update.mjs +43 -19
  22. package/src/commands/validate.mjs +47 -35
  23. package/src/config.mjs +40 -6
  24. package/src/lib/board.mjs +88 -26
  25. package/src/lib/claude.mjs +315 -70
  26. package/src/lib/critique.mjs +391 -91
  27. package/src/lib/decomposition-doc.mjs +451 -0
  28. package/src/lib/implement-board.mjs +14 -1
  29. package/src/lib/project-root.mjs +93 -0
  30. package/src/lib/templates.mjs +53 -0
  31. package/src/setup/files.mjs +3 -10
  32. package/src/templates/skill/SKILL.md +137 -61
  33. package/src/templates/workflows/code-review.yml +1 -1
  34. package/src/templates/workflows/decompose.yml +20 -6
  35. package/src/templates/workflows/generate-plan.yml +1 -1
  36. package/src/templates/workflows/generate-spec.yml +1 -1
  37. package/src/templates/workflows/qa.yml +1 -1
  38. package/src/templates/workflows/validate.yml +1 -1
  39. package/src/lib/feature-docs.mjs +0 -89
  40. package/src/lib/force.mjs +0 -34
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: spec-wave
3
3
  description: "Use when the user wants to set up a spec-driven GitHub workflow, create a Feature issue, generate spec.md or plan.md, decompose a Feature into Stories/Tasks, write RFC documentation, or audit and fix a Pull Request. Implements the RFC-001 workflow with GitHub Projects v2, labels, and AI-powered GitHub Actions."
4
- argument-hint: "[info|setup|update|doctor|issue|feature|spec|plan|ready|decompose|order|implement|task|story|uninstall|rfc|fix-pr] [target]"
4
+ argument-hint: "[info|setup|update|doctor|issue|feature|spec|plan|ready|decompose|order|implement|task|story|move|uninstall|rfc|fix-pr] [target]"
5
5
  user-invocable: true
6
6
  allowed-tools:
7
7
  - Bash(npx @spec-wave/cli@latest *)
@@ -87,33 +87,29 @@ Essa é a sequência completa, mas **cada tipo de artefato percorre só um trech
87
87
  |----------|----------|----------|-------------|
88
88
  | **Initiative / Epic** | 📥 Backlog | — | Agrupadores. Não têm fluxo próprio; acompanham os filhos. |
89
89
  | **Feature** | 📥 Backlog | 🎯 Priorizado → 📋 Spec → 📋 Plan → ✅ Ready → 🚧 Desenvolvimento → 👀 Code Review → 🧪 QA → 📋 Homologação → 🚀 Deploy → 🎉 Done | Fica parada em **✅ Ready** esperando um dev assumir — é ali que ela aparece na fila do TL. Só avança para 👀 Code Review quando **TODAS** as suas Stories já estiverem lá. |
90
- | **Story** | **✅ Ready** (criada pelo `decompose`) | 🚧 Desenvolvimento → 👀 Code Review → 🧪 QA → 📋 Homologação → 🎉 Done | **Nunca nasce em 📥 Backlog.** |
91
- | **Task** | **✅ Ready** (criada pelo `decompose`) | 🚧 Desenvolvimento → 🎉 Done | **Não** passa por Code Review, QA nem Homologação — só `task start` e `task done`. |
90
+ | **Story** | **✅ Ready** (criada pelo `decompose-apply`) | 🚧 Desenvolvimento → 👀 Code Review → 🧪 QA → 📋 Homologação → 🎉 Done | **Nunca nasce em 📥 Backlog.** |
91
+ | **Task** | **✅ Ready** (criada pelo `decompose-apply`) | 🚧 Desenvolvimento → 🎉 Done | **Não** passa por Code Review, QA nem Homologação — só `task start` e `task done`. |
92
92
  | **RFC** | 📥 Backlog | decompõe direto em **Tasks** (que nascem em ✅ Ready) | Não usa spec/plan. |
93
93
  | **Bug** | 📥 Backlog | 🚧 Desenvolvimento → 🎉 Done | Ao aprovar, vai direto para Done (não passa por Homologação). |
94
94
  | **Spike** | 📥 Backlog | **movido só à mão pelo usuário** | Nunca avance a Etapa de um Spike por conta própria. |
95
95
 
96
- **Regra da Etapa:** a Etapa **só avança, nunca retrocede**. O campo **Status** (Todo / In Progress / Done) mede o progresso *dentro* da Etapa e reinicia a cada avanço. Prefira sempre os comandos da CLI (`task start|done`, `story review`) a mutações manuais no board — eles embutem essas regras.
96
+ **Regra da Etapa:** a Etapa **só avança, nunca retrocede**. O campo **Status** (Todo / In Progress / Done) mede o progresso *dentro* da Etapa e reinicia a cada avanço. Prefira sempre os comandos da CLI (`move`, `task start|done`, `story review`) a mutações manuais no board — eles embutem essas regras.
97
97
 
98
98
  Labels de gatilho:
99
99
  - `spec-wave:spec` → dispara `generate-spec.yml` → gera `spec.md` (especificação funcional, primeiro)
100
100
  - `spec-wave:plan` → dispara `generate-plan.yml` → gera `plan.md` (plano técnico, a partir da spec)
101
101
  - `spec-wave:ready` → dispara `validate.yml` → valida ambos os arquivos
102
- - `spec-wave:decompose` → dispara `decompose.yml` → gera Stories e Tasks
102
+ - `spec-wave:decompose` → dispara `decompose.yml` → gera (ou **re-critica**) o **rascunho** em `decomposition.md`. **Não cria issue nenhuma.**
103
+ - `spec-wave:decompose-apply` → dispara o mesmo workflow em modo aplicação → cria as Stories e Tasks **a partir do rascunho revisado**
103
104
 
104
105
  Labels de **estado** (gravadas pelas automações — **não** são gatilhos, não as adicione por conta própria):
105
- - `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)
106
+ - `spec-wave:decompose-ready` → o rascunho da decomposição passou pela crítica e espera **revisão humana**; aplique `spec-wave:decompose-apply` para criar as issues
107
+ - `spec-wave:critique-failed` → a crítica adversarial apontou contradições **graves**; **bloqueia** o `spec-wave:ready` até ser removida (veja *Crítica adversarial* abaixo)
108
+ - `spec-wave:needs-human` → a crítica reprovou N vezes seguidas (default 3); **para o fluxo** até uma pessoa revisar e remover a label
106
109
  - `spec-wave:decomposed` → a Feature/RFC já foi decomposta; o `decompose` pula silenciosamente enquanto ela existir (veja *Guard de idempotência* abaixo)
107
110
 
108
- Label **modificadora** — `spec-wave:force`: sozinha **não dispara nada**; adicionada **junto** de uma label de gatilho, manda o comando **re-executar a etapa ignorando os guards**. É **consumida** (removida) pelo run que a leu, então vale para uma execução só. Adicione-a **antes ou junto** do gatilho, nunca depois (o evento do gatilho já teria disparado):
109
- ```bash
110
- gh issue edit <n> --add-label "spec-wave:force" --add-label "spec-wave:decompose"
111
- ```
112
- Efeito por comando:
113
- - **spec** e **plan** → geram a **próxima versão** em vez de sobrescrever: `spec.md` (v1) → `spec-v2.md` → `spec-v3.md`… As versões anteriores ficam intactas e a **maior versão passa a ser o documento atual** (veja *Documentos versionados*).
114
- - **decompose** → ignora o guard e **fecha as sub-issues da decomposição anterior** antes de gerar as novas (veja *Guard de idempotência*).
115
-
116
- Sem a label, `spec`/`plan` **sobrescrevem o documento atual** (comportamento de sempre).
111
+ Label **modificadora** (esta você pode aplicar):
112
+ - `spec-wave:model:<apelido>` → força um modelo específico **naquela execução**, resolvido por `ai.modelAliases` no `.spec-wave.json`. Serve para reprocessar um caso difícil num modelo mais forte sem editar a configuração do repositório inteiro. Duas dessas labels na mesma issue = ambíguo, nenhuma vale.
117
113
 
118
114
  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`.
119
115
 
@@ -193,7 +189,7 @@ Mesmas flags do `issue` (exceto `--type`, fixo em `feature`). Mantido para o flu
193
189
  | Flag | Tipo | Descrição |
194
190
  |------|------|-----------|
195
191
  | `--issue-number <n>` | string (obrigatório) | Número da issue no GitHub. |
196
- | `--force` | flag | Re-executa a etapa ignorando os guards. No fluxo por label o equivalente é a label `spec-wave:force` (a linha de comando do workflow é fixa) — prefira a label. |
192
+ | `--apply` | flag (só `decompose`) | Cria as issues a partir do `decomposition.md` revisado. **Sem** a flag, o `decompose` apenas gera/critica o rascunho e não cria nada. |
197
193
 
198
194
  > ⚠️ Esses quatro comandos são executados pelos **GitHub Actions** (disparados por labels), **não** pela skill diretamente. Veja a *Regra fundamental*: para gerar plan/spec/decompor, adicione a **label** correspondente — não rode o comando à mão (a não ser para debug local).
199
195
  >
@@ -206,12 +202,12 @@ Mesmas flags do `issue` (exceto `--type`, fixo em `feature`). Mantido para o flu
206
202
  |----------|------|-----------|
207
203
  | `<issue>` | string (obrigatório) | Número da issue (Feature, Story ou Task), ex.: `12` ou `#12`. Argumento posicional. |
208
204
  | `--feature-dir <path>` | string | Caminho `docs/features/<slug>` para anexar `spec.md`/`plan.md` como contexto (sobrescreve a resolução automática). |
209
- | `--dry-run` | flag | Monta o contexto e imprime o comando do spec-kit **sem executar**. |
205
+ | `--dry-run` | flag | Monta o contexto e imprime o comando do spec-kit **sem executar** e **sem escrever nada no GitHub** (não move cards, não adiciona itens ao Project). |
210
206
 
211
207
  > 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.
212
208
 
213
209
  ### `@spec-wave/cli doctor` — preflight de auth e configuração (comando LOCAL)
214
- 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`/teto de saída + 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.
210
+ 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`/escalada da crítica/apelidos de modelo/teto de saída + secrets do Actions), **higiene do board e das labels** (colunas fora do fluxo canônico, labels `spec-wave:*` descontinuadas ou ausentes), **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 os workflows (presença + **versão da CLI fixada**, não `@latest`).
215
211
 
216
212
  > Saída: `✓` ok, `✗` problema confirmado, `!` não verificável (best-effort — falha de rede nunca derruba o doctor). **Exit 1** se houver algum `✗`. **Quando rodar:** no início de uma sessão de trabalho, ou sempre que aparecer um erro estranho (ex.: **404 ao criar issues** — causa típica: token sem acesso ao repo/org, que o doctor aponta). É o primeiro passo de troubleshooting — prefira-o a depurar `gh api` na mão.
217
213
 
@@ -238,43 +234,88 @@ Sem flags. Roda um checklist de diagnóstico no repositório atual: token GitHub
238
234
 
239
235
  > Avança a Story para a Etapa **👀 Code Review** com Status **Todo** (o Status reinicia ao trocar de etapa). Mesma regra do `task`: a **Etapa nunca retrocede** — se a Story já estiver em Code Review ou adiante, o comando apenas informa a Etapa atual. Use no fim do `implement` de uma Story, em vez de mutações GraphQL manuais.
240
236
 
237
+ ### `@spec-wave/cli move <n> <etapa>` — move QUALQUER item do board (comando LOCAL)
238
+ | Flag/Arg | Tipo | Descrição |
239
+ |----------|------|-----------|
240
+ | `<n>` | string (obrigatório) | Número da issue, ex.: `8` ou `#8`. |
241
+ | `<etapa>` | string (obrigatório) | Etapa de destino, com ou sem emoji, sem acento e em qualquer caixa: `"code review"`, `"Homologação"`, `"🎉 Done"`. |
242
+ | `--status <valor>` | string | Valor do campo Status no destino: `Todo`, `In Progress` ou `Done` (default: `Todo`). |
243
+
244
+ > Serve para **Feature, Story, Task, Bug e RFC** — é o comando a usar quando `story review` e `task start|done` não cobrem o movimento desejado (ex.: mover uma **Feature** para Homologação). Embute as mesmas guardas: a **Etapa nunca retrocede** (se o item já estiver adiante, o comando informa a Etapa atual e não faz nada) e **Spike** é recusado (sua Etapa é movida à mão). Um nome de etapa ambíguo (`"p"` casa com Priorizado e Plan) é recusado listando as candidatas.
245
+ >
246
+ > **Prefira `move` a `gh api graphql` manual.** Não existe escape hatch para retroceder — isso é a regra do fluxo, não uma limitação.
247
+
241
248
  ---
242
249
 
243
- ## Crítica adversarial, idempotência e dependências (v0.7)
250
+ ## Crítica adversarial, decomposição em duas etapas e dependências
244
251
 
245
252
  ### Crítica adversarial (comentário 🔎)
246
253
 
247
- Após o `generate-plan` e **antes** da criação de issues no `decompose`, um segundo agente de IA critica os documentos procurando contradições, lacunas e riscos. O resultado vira um comentário **🔎 Crítica adversarial (spec-wave)** na issue.
254
+ Depois do `generate-plan` e **antes** de qualquer issue ser criada no `decompose`, um segundo agente de IA audita o artefato procurando contradições com a spec, o plan e o `tech_context`. O resultado vira um comentário **🔎 Crítica adversarial (spec-wave)** na issue, com cabeçalho informando a **tentativa** e o **modelo** usados.
255
+
256
+ A saída da crítica é **estruturada e validada por schema**: `severity` só aceita `grave` ou `menor`, e um payload fora do contrato **falha alto** em vez de virar um finding menor com JSON cru dentro. Se a crítica não conclui, ela **não** é registrada como aprovada.
257
+
258
+ **A remediação depende de ONDE a crítica reprovou — são superfícies diferentes:**
248
259
 
249
- - Findings **graves** o Action aplica a label **`spec-wave:critique-failed`**, que **bloqueia o `spec-wave:ready`** (o `validate` falha enquanto ela existir).
250
- - **Fluxo de resolução:** (1) leia o comentário 🔎 na issue; (2) corrija `spec.md`/`plan.md` (regenere com as labels ou edite e commite); (3) remova a label: `gh issue edit <n> --remove-label "spec-wave:critique-failed"`; (4) re-aplique `spec-wave:ready` para validar de novo.
251
- - Findings leves não bloqueiam trate-os como revisão de qualidade.
260
+ | Reprovou depois de | O que corrigir | Como retomar |
261
+ |--------------------|----------------|--------------|
262
+ | `generate-plan` | `plan.md` (ou a `spec.md` que o embasa) | corrija/regere → remova `spec-wave:critique-failed` reaplique `spec-wave:ready` |
263
+ | `decompose` (rascunho) | **`decomposition.md`** — os achados citam **`Story N`** e **`Task N.M`**, que são os títulos desse arquivo | edite o arquivo e commite → remova `spec-wave:critique-failed` → reaplique `spec-wave:decompose` (ele **critica o arquivo como está**, sem regerar) |
252
264
 
253
- ### Documentos versionados (`spec-v2.md`, `plan-v3.md`…)
265
+ > ⚠️ No contexto do `decompose`, os achados são sobre as **Stories propostas**, não sobre o `plan.md`. Corrigir o plan não influencia o rascunho já gravado — edite o `decomposition.md` diretamente. Foi exatamente essa confusão que fazia o ciclo não convergir.
254
266
 
255
- A primeira geração escreve `spec.md` e `plan.md` a **v1**. Cada regeração **forçada** (label `spec-wave:force`) grava a **próxima versão** ao lado, sem tocar nas anteriores:
267
+ - Findings **graves** no `decompose` **nenhuma Story/Task é criada** e o Action **falha (exit 1)**, com a label `spec-wave:critique-failed`. Um pipeline bloqueado nunca fica verde.
268
+ - Findings **menores** não bloqueiam — trate-os como revisão de qualidade.
269
+ - **Escalada:** na tentativa 2 a crítica roda em `ai.escalationModel` (se configurado). Ao atingir `ai.maxCritiqueAttempts` (default 3), o fluxo aplica **`spec-wave:needs-human`**, comenta e **para** — sem chamar a IA de novo. Para retomar: corrija os documentos e remova **as duas** labels (`spec-wave:needs-human` e `spec-wave:critique-failed`).
270
+ - Uma crítica **limpa** remove a `spec-wave:critique-failed` automaticamente e zera o contador de tentativas.
271
+
272
+ ### Decomposição em duas etapas (`decomposition.md`)
273
+
274
+ O `decompose` **não cria issues direto**. São dois passos, com um artefato revisável no meio:
256
275
 
257
276
  ```
258
- docs/features/<slug>/
259
- spec.md v1
260
- spec-v2.md regeração forçada
261
- spec-v3.md ← regeração forçada ⬅ spec ATUAL
262
- plan.md plano ATUAL (spec e plan versionam independente)
277
+ spec-wave:decompose
278
+ ├─ decomposition.md ausente → gera via IA → commita → critica
279
+ └─ decomposition.md presente critica o arquivo COMO ESTÁ (preserva suas edições)
280
+ ├─ grave → +critique-failed, comentário citando Story N / Task N.M, exit 1
281
+ └─ limpo → +decompose-ready, comentário "revise e aplique"
282
+
283
+ spec-wave:decompose-apply
284
+ └─ lê decomposition.md → cria Stories/Tasks → board → +decomposed
285
+ (sem nova crítica: aplicar a label É a aprovação humana)
263
286
  ```
264
287
 
265
- **A maior versão é sempre o documento atual** — é ela que o `generate-plan` usa como contexto, que o `validate` valida, que o `decompose` lê e que o `implement` anexa. Uma regeração **sem** force sobrescreve essa versão atual (não volta para o `spec.md` original).
288
+ O arquivo fica em **`docs/features/<slug>/decomposition.md`** (Feature) ou **`docs/rfcs/<slug>/decomposition.md`** (RFC). Formato:
266
289
 
267
- Ao mostrar os documentos ao usuário, use a versão atual; ofereça as anteriores só se ele quiser comparar. Nunca renomeie nem apague versões antigas por conta própria — elas são o histórico da Feature.
290
+ ```markdown
291
+ # Decomposição — [FEATURE] Cadastro de Pedidos
292
+ <!-- spec-wave:decomposition v1 issue=360 kind=stories -->
268
293
 
269
- ### Guard de idempotência do decompose (`spec-wave:decomposed`)
294
+ ## Story 1 visualizar meus pedidos
270
295
 
271
- O evento `labeled` pode redisparar (re-add da label, retry de runner). Para não duplicar Stories/Tasks, o `decompose` **pula** quando a issue já tem a label **`spec-wave:decomposed`** ou já tem sub-issues do tipo-alvo (`[STORY]` para Feature, `[TASK]` para RFC). Ao concluir com sucesso, o Action grava a label. Os workflows ainda usam `concurrency` por issue para serializar runs simultâneos.
296
+ **User story:** Como cliente, quero visualizar meus pedidos, para acompanhar entregas
297
+ **Depende de:** —
272
298
 
273
- **Para forçar um re-decompose**, adicione a label `spec-wave:force` junto com o gatilho:
274
- ```bash
275
- gh issue edit <n> --add-label "spec-wave:force" --add-label "spec-wave:decompose"
299
+ Descrição complementar (contexto, critérios de aceite).
300
+
301
+ ### Task 1.1 criar endpoint GET /pedidos
302
+
303
+ Corpo técnico.
276
304
  ```
277
- O comando então: (1) ignora os dois guards; (2) **fecha** as sub-issues abertas do tipo-alvo da decomposição anterior **e os filhos delas** (as Tasks de cada Story), comentando na issue o que foi fechado; (3) gera a nova decomposição. **É destrutivo** — se alguma Story já tiver saído de ✅ Ready, o comentário destaca quais, para você conferir se descartou trabalho em andamento. Confirme com o usuário antes de acionar em Features com desenvolvimento em curso.
305
+
306
+ **Ao editar à mão:**
307
+ - a **posição** manda, não o número escrito — inserir uma Story no meio sem renumerar funciona;
308
+ - `**Depende de:**` usa referências **1-based** (`Story 1, Story 3`) ou `—`; apontar para si mesma ou para frente é **erro**, não filtro silencioso;
309
+ - o corpo aceita markdown livre (`## Backend`, cercas de código) — só `## Story N` e `### Task N.M` são estrutura;
310
+ - **para gerar outro rascunho do zero:** apague o arquivo e reaplique `spec-wave:decompose`.
311
+
312
+ ### Guard de idempotência do decompose (`spec-wave:decomposed`)
313
+
314
+ O evento `labeled` pode redisparar (re-add da label, retry de runner). Para não duplicar Stories/Tasks, o `decompose` **pula** quando a issue já tem a label **`spec-wave:decomposed`** ou já tem sub-issues do tipo-alvo (`[STORY]` para Feature, `[TASK]` para RFC). O Action grava a label ao **aplicar** a decomposição. Os workflows ainda usam `concurrency` por issue para serializar runs simultâneos.
315
+
316
+ A `spec-wave:decompose-ready` **não** entra nesse guard: é estado de rascunho pendente, não de decomposição feita.
317
+
318
+ **Para forçar um re-decompose:** remova a label (`gh issue edit <n> --remove-label "spec-wave:decomposed"`), **apague/feche as sub-issues antigas** (senão a detecção por sub-issues pula de novo), apague o `decomposition.md` se quiser um rascunho novo, e re-adicione `spec-wave:decompose`.
278
319
 
279
320
  ### Dependências entre Stories (`Depende de: #N`)
280
321
 
@@ -297,12 +338,37 @@ Cada ação de IA (`spec`, `plan`, `decompose`, `critique`) pode usar um modelo
297
338
  "models": {
298
339
  "plan": "claude-opus-4-1",
299
340
  "critique": "claude-opus-4-1"
341
+ },
342
+ "escalationModel": "claude-opus-5",
343
+ "maxCritiqueAttempts": 3,
344
+ "modelAliases": {
345
+ "opus": "claude-opus-5",
346
+ "haiku": "claude-haiku-4-5"
300
347
  }
301
348
  }
302
349
  }
303
350
  ```
304
351
 
305
- Edite o bloco `ai` no `.spec-wave.json` (e commite) — o `doctor` mostra o provider, o modelo e os overrides de `ai.models` resolvidos.
352
+ - `escalationModel` modelo usado a partir da **segunda** tentativa da crítica.
353
+ - `maxCritiqueAttempts` → quantas reprovas seguidas antes de exigir revisão humana (`spec-wave:needs-human`).
354
+ - `modelAliases` → apelidos para a label **`spec-wave:model:<apelido>`**.
355
+
356
+ **Precedência do modelo:** `SPEC_WAVE_MODEL` (env) → label `spec-wave:model:<apelido>` → escalada automática da crítica → `ai.models[ação]` → `ai.model` → default do provider.
357
+
358
+ Edite o bloco `ai` no `.spec-wave.json` (e commite) — o `doctor` mostra o provider, o modelo, os apelidos, a escalada e os overrides resolvidos.
359
+
360
+ ### Override de modelo numa execução (`spec-wave:model:<apelido>`)
361
+
362
+ Para reprocessar **uma issue difícil** num modelo mais forte sem editar a configuração do repositório inteiro:
363
+
364
+ ```bash
365
+ # 1. Garanta o apelido em ai.modelAliases (uma vez, commitado)
366
+ # 2. Aplique a label na issue e redispare o gatilho
367
+ gh issue edit 360 --add-label "spec-wave:model:opus"
368
+ gh issue edit 360 --add-label "spec-wave:decompose"
369
+ ```
370
+
371
+ A label vale só para as execuções daquela issue e **vence a escalada automática** (é decisão humana explícita). Apelido desconhecido é ignorado com aviso; duas labels dessas na mesma issue = ambíguo, nenhuma vale.
306
372
 
307
373
  ### Teto de saída (`ai.maxTokens`) e truncamento
308
374
 
@@ -436,11 +502,7 @@ Inicia a geração da **especificação funcional** para uma Feature. É o **pri
436
502
  ```
437
503
  3. Informe: "Label `spec-wave:spec` adicionada. O GitHub Action `generate-spec.yml` irá gerar o `spec.md` automaticamente."
438
504
  4. Após a conclusão, ofereça revisar o spec.md gerado em `docs/features/<slug>/spec.md`.
439
- 5. **Para regerar** (a spec já existe e o usuário quer outra versão): adicione `spec-wave:force` junto com o gatilho o Action grava a **próxima versão** (`spec-v2.md`, `spec-v3.md`…) e preserva as anteriores; a nova passa a ser a spec atual. Sem o force, a versão atual é **sobrescrita** (edições manuais se perdem) — avise antes de acionar assim.
440
- ```bash
441
- gh issue edit <número> --add-label "spec-wave:force" --add-label "spec-wave:spec"
442
- ```
443
- 6. Próximo passo: gerar o plano técnico — mova para **📋 Plan** e use `/spec-wave plan <número>`.
505
+ 5. Próximo passo: gerar o plano técnicomova para **📋 Plan** e use `/spec-wave plan <número>`.
444
506
 
445
507
  ---
446
508
 
@@ -459,8 +521,7 @@ O plano técnico segue o schema do RFC-002 §3.2: **Estratégia Técnica** (com
459
521
  ```
460
522
  4. Informe: "Label `spec-wave:plan` adicionada. O GitHub Action `generate-plan.yml` irá gerar o `plan.md` automaticamente. Acompanhe em: Actions → Generate Plan."
461
523
  5. Após a conclusão (cheque comentários na issue ou aguarde confirmação do usuário), ofereça revisar o plan.md gerado em `docs/features/<slug>/plan.md`.
462
- 6. **Para regerar**: adicione `spec-wave:force` junto com `spec-wave:plan`grava `plan-v2.md`, `plan-v3.md`… preservando as anteriores. Sem o force, o plano atual é sobrescrito (confirme antes).
463
- 7. Próximo passo: validar a Feature — mova para **✅ Ready** e use `/spec-wave ready <número>`.
524
+ 6. Próximo passo: validar a Featuremova para **✅ Ready** e use `/spec-wave ready <número>`.
464
525
 
465
526
  ---
466
527
 
@@ -531,28 +592,39 @@ Valida que spec.md e plan.md estão completos e a Feature pode avançar.
531
592
  ```
532
593
  2. Informe: "Validação iniciada. O workflow verificará se spec.md e plan.md contêm todas as seções obrigatórias."
533
594
  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.
534
- 4. **Se a issue tiver a label `spec-wave:critique-failed`**, a validação falha de imediato: a crítica adversarial apontou contradições graves (comentário 🔎 na issue). Siga o fluxo de resolução da seção *Crítica adversarial*: corrigir os documentos → remover a label → re-aplicar `spec-wave:ready`.
535
- 5. Se passar, oriente: "Feature validada! Mova o card para **✅ Ready** e use `/spec-wave decompose <número>` para gerar as Stories."
595
+ 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`).
596
+ 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)."
536
597
 
537
598
  ---
538
599
 
539
600
  ### `/spec-wave decompose <número-da-issue>`
540
601
 
541
- Decompõe automaticamente. Aplica-se a **dois tipos**:
542
- - **Feature** → gera **Stories** (cada uma com suas **Tasks**), a partir de `spec.md` + `plan.md`.
543
- - **RFC** → gera **Tasks diretamente** (sem Stories), a partir da descrição do RFC.
602
+ Decompõe em **duas etapas**, com um rascunho revisável no meio (veja *Decomposição em duas etapas*). Aplica-se a **dois tipos**:
603
+ - **Feature** → **Stories** (cada uma com suas **Tasks**), a partir de `spec.md` + `plan.md`.
604
+ - **RFC** → **Tasks diretamente** (sem Stories), a partir da descrição do RFC.
544
605
 
545
606
  Para qualquer outro tipo (Spike, Bug, Story, Task, …) o Action **recusa** e comenta.
546
607
 
547
608
  **Passos:**
548
609
  1. Para **Feature**: confirme que está em **✅ Ready** (spec.md e plan.md validados). Para **RFC**: basta a descrição estar completa (RFC não usa spec/plan).
549
- 2. Adicione a label de decomposição:
610
+ 2. **Etapa 1 gerar o rascunho:**
550
611
  ```bash
551
612
  gh issue edit <número> --add-label "spec-wave:decompose"
552
613
  ```
553
- 3. Informe: "Decomposição iniciadaFeature gera Stories+Tasks; RFC gera Tasks."
554
- 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.
555
- 5. A issue recebe a label `spec-wave:decomposed` (guard de idempotência): rodar de novo **não** duplica as issues. Para re-decompor, adicione `spec-wave:force` junto com o gatilho isso **fecha** a decomposição anterior antes de gerar a nova (veja *Guard de idempotência*; confirme com o usuário, é destrutivo).
614
+ Informe: "Rascunho iniciadovai commitar `decomposition.md` e passar pela crítica. **Nenhuma issue é criada nesta etapa.**"
615
+ 3. Quando o Action terminar, leia o comentário na issue:
616
+ - **`spec-wave:decompose-ready`** o rascunho passou pela crítica. **Leia o `decomposition.md`** e mostre ao usuário o que será criado (Stories, Tasks, dependências). Ofereça editar o arquivo antes de aplicar.
617
+ - **`spec-wave:critique-failed`** → o Action **falhou (exit 1)** e nada foi criado. Os achados citam `Story N` / `Task N.M` do `decomposition.md`. **Corrija esse arquivo** (não o `plan.md`), commite e reaplique `spec-wave:decompose` — o arquivo é criticado como está, sem ser regerado.
618
+ - **`spec-wave:needs-human`** → a crítica esgotou as tentativas. Pare e envolva o usuário: as duas labels precisam sair à mão.
619
+ 4. **Etapa 2 — aplicar o rascunho aprovado** (só depois da revisão):
620
+ ```bash
621
+ gh issue edit <número> --add-label "spec-wave:decompose-apply"
622
+ ```
623
+ Aplicar essa label **é** a aprovação humana — não há nova crítica.
624
+ 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.
625
+ 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*.
626
+
627
+ > **Nunca pule a etapa 1** aplicando `spec-wave:decompose-apply` direto: sem `decomposition.md` o Action falha pedindo o rascunho.
556
628
 
557
629
  ---
558
630
 
@@ -718,12 +790,16 @@ Audita um Pull Request e corrige automaticamente os problemas encontrados — se
718
790
  docs/
719
791
  features/
720
792
  <slug-da-feature>/
721
- spec.md ← gerado quando spec-wave:spec é adicionado (1º) = v1
722
- spec-v2.md regeração forçada (spec-wave:force); v1 preservada
723
- plan.md ← gerado quando spec-wave:plan é adicionado (2º, usa a spec ATUAL) = v1
724
- plan-v2.md ← regeração forçada do plano
793
+ spec.md ← gerado quando spec-wave:spec é adicionado (1º)
794
+ plan.md gerado quando spec-wave:plan é adicionado (2º, usa a spec)
795
+ decomposition.md rascunho gerado quando spec-wave:decompose é adicionado ().
796
+ REVISÁVEL e EDITÁVEL à mão; as issues só nascem com
797
+ spec-wave:decompose-apply
798
+ rfcs/
799
+ <slug-do-rfc>/
800
+ decomposition.md ← mesmo papel, com "## Task N" (RFC não usa spec/plan)
725
801
  ```
726
802
 
727
- A **maior versão de cada tipo é o documento atual** (aqui: `spec-v2.md` e `plan-v2.md`) — veja *Documentos versionados*.
728
-
729
803
  O slug é gerado a partir do título da issue: `[FEATURE] Cadastro de Pedidos com PIX` → `cadastro-de-pedidos-com-pix`
804
+
805
+ > ⚠️ O slug vem do **título**. Renomear a Feature entre o rascunho e o apply muda o diretório e o `decomposition.md` antigo fica órfão — o apply reclama que não encontrou o rascunho.
@@ -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@latest code-review --pr-number ${{ github.event.pull_request.number }}
27
+ run: npx @spec-wave/cli@{{CLI_VERSION}} 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 }}
@@ -4,8 +4,9 @@ on:
4
4
  issues:
5
5
  types: [labeled]
6
6
 
7
- # O evento `labeled` pode redisparar (re-add da label, retry de runner):
8
- # o concurrency serializa runs da mesma issue e o guard de idempotência
7
+ # O evento `labeled` pode redisparar (re-add da label, retry de runner). O
8
+ # concurrency serializa runs da mesma issue inclusive um `decompose-apply`
9
+ # enfileirado atrás de um `decompose` — e o guard de idempotência
9
10
  # (label spec-wave:decomposed) descarta o run enfileirado.
10
11
  concurrency:
11
12
  group: spec-wave-decompose-${{ github.event.issue.number }}
@@ -13,23 +14,36 @@ concurrency:
13
14
 
14
15
  jobs:
15
16
  decompose:
17
+ # Duas etapas, duas labels:
18
+ # • spec-wave:decompose → grava/re-critica docs/**/decomposition.md
19
+ # • spec-wave:decompose-apply → cria as Stories/Tasks a partir do rascunho
20
+ # O `[RFC]` no título também dispara: o caminho RFC → Tasks existe na CLI
21
+ # desde sempre, mas era inalcançável porque o filtro exigia `[FEATURE]`.
16
22
  if: >
17
- github.event.label.name == 'spec-wave:decompose' &&
18
- contains(github.event.issue.title, '[FEATURE]')
23
+ (github.event.label.name == 'spec-wave:decompose' ||
24
+ github.event.label.name == 'spec-wave:decompose-apply') &&
25
+ (contains(github.event.issue.title, '[FEATURE]') ||
26
+ contains(github.event.issue.title, '[RFC]'))
19
27
  runs-on: ubuntu-latest
20
28
  permissions:
21
29
  issues: write
22
- contents: read
30
+ # A etapa de rascunho commita docs/**/decomposition.md no branch default.
31
+ contents: write
23
32
 
24
33
  steps:
25
34
  - uses: actions/checkout@v4
35
+ with:
36
+ token: ${{ secrets.GITHUB_TOKEN }}
26
37
 
27
38
  - uses: actions/setup-node@v4
28
39
  with:
29
40
  node-version: '24'
30
41
 
31
42
  - name: Decompose into Stories and Tasks
32
- run: npx @spec-wave/cli@latest decompose --issue-number ${{ github.event.issue.number }}
43
+ run: >
44
+ npx @spec-wave/cli@{{CLI_VERSION}} decompose
45
+ --issue-number ${{ github.event.issue.number }}
46
+ ${{ github.event.label.name == 'spec-wave:decompose-apply' && '--apply' || '' }}
33
47
  env:
34
48
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35
49
  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@latest generate-plan --issue-number ${{ github.event.issue.number }}
31
+ run: npx @spec-wave/cli@{{CLI_VERSION}} 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@latest generate-spec --issue-number ${{ github.event.issue.number }}
31
+ run: npx @spec-wave/cli@{{CLI_VERSION}} 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@latest qa --pr-number ${{ github.event.pull_request.number }}
28
+ run: npx @spec-wave/cli@{{CLI_VERSION}} 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@latest validate --issue-number ${{ github.event.issue.number }}
29
+ run: npx @spec-wave/cli@{{CLI_VERSION}} validate --issue-number ${{ github.event.issue.number }}
30
30
  env:
31
31
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32
32
  GITHUB_REPOSITORY: ${{ github.repository }}
@@ -1,89 +0,0 @@
1
- // Resolução dos documentos versionados de uma Feature (docs/features/<slug>/).
2
- //
3
- // A primeira geração escreve `spec.md`/`plan.md` (= v1). Cada regeração
4
- // FORÇADA (flag --force ou label spec-wave:force) cria a próxima versão em vez
5
- // de sobrescrever: `spec-v2.md`, `spec-v3.md`, … O documento ATUAL é sempre a
6
- // MAIOR versão — é ele que validate/generate-plan/decompose/implement leem, e
7
- // é ele que uma regeração sem force sobrescreve.
8
- import { existsSync, readdirSync } from 'node:fs';
9
- import path from 'node:path';
10
-
11
- /**
12
- * Versão de um arquivo de documento (função PURA).
13
- * `spec.md` → 1; `spec-v2.md` → 2. Outro nome → null.
14
- *
15
- * @param {string} filename nome do arquivo (sem diretório)
16
- * @param {string} base 'spec' ou 'plan'
17
- * @returns {number|null}
18
- */
19
- export function parseDocVersion(filename, base) {
20
- const match = new RegExp(`^${base}(?:-v(\\d+))?\\.md$`).exec(String(filename ?? ''));
21
- if (!match) return null;
22
- return match[1] ? parseInt(match[1], 10) : 1;
23
- }
24
-
25
- /**
26
- * Documento ATUAL entre os arquivos dados: o de maior versão (função PURA).
27
- * Empate (raro: `spec.md` e `spec-v1.md` coexistindo) resolve pelo sufixado,
28
- * para o resultado ser determinístico.
29
- *
30
- * @param {string[]} filenames
31
- * @param {string} base 'spec' ou 'plan'
32
- * @returns {{ file: string, version: number }|null}
33
- */
34
- export function pickLatestDoc(filenames, base) {
35
- const found = (filenames || [])
36
- .map(file => ({ file, version: parseDocVersion(file, base), suffixed: /-v\d+\.md$/.test(file) }))
37
- .filter(entry => entry.version !== null)
38
- .sort((a, b) => (a.version - b.version) || (Number(a.suffixed) - Number(b.suffixed)));
39
- if (found.length === 0) return null;
40
- const { file, version } = found[found.length - 1];
41
- return { file, version };
42
- }
43
-
44
- /**
45
- * Nome do arquivo da PRÓXIMA versão (função PURA): `spec.md` quando ainda não
46
- * há nenhum, `spec-v<N+1>.md` a partir da maior versão existente.
47
- *
48
- * @param {string[]} filenames
49
- * @param {string} base 'spec' ou 'plan'
50
- * @returns {string}
51
- */
52
- export function nextDocName(filenames, base) {
53
- const latest = pickLatestDoc(filenames, base);
54
- return latest ? `${base}-v${latest.version + 1}.md` : `${base}.md`;
55
- }
56
-
57
- // Nomes de arquivo do diretório da feature ([] se ele ainda não existe).
58
- function readDir(featureDir) {
59
- return featureDir && existsSync(featureDir) ? readdirSync(featureDir) : [];
60
- }
61
-
62
- /**
63
- * Caminho do documento ATUAL (maior versão) — null se nenhum existe.
64
- *
65
- * @returns {{ path: string, version: number }|null}
66
- */
67
- export function resolveDoc(featureDir, base) {
68
- const latest = pickLatestDoc(readDir(featureDir), base);
69
- return latest ? { path: path.join(featureDir, latest.file), version: latest.version } : null;
70
- }
71
-
72
- /**
73
- * Caminho onde a geração deve escrever.
74
- * - `force: true` → próxima versão (nunca sobrescreve nada);
75
- * - `force: false` → o documento atual (sobrescreve), ou `<base>.md` no 1º run.
76
- *
77
- * @returns {{ path: string, version: number, isNewVersion: boolean }}
78
- */
79
- export function resolveWritePath(featureDir, base, { force = false } = {}) {
80
- const files = readDir(featureDir);
81
- if (!force) {
82
- const latest = pickLatestDoc(files, base);
83
- return latest
84
- ? { path: path.join(featureDir, latest.file), version: latest.version, isNewVersion: false }
85
- : { path: path.join(featureDir, `${base}.md`), version: 1, isNewVersion: false };
86
- }
87
- const name = nextDocName(files, base);
88
- return { path: path.join(featureDir, name), version: parseDocVersion(name, base), isNewVersion: true };
89
- }
package/src/lib/force.mjs DELETED
@@ -1,34 +0,0 @@
1
- // Detecção do modo "force" (re-executar a etapa ignorando os guards).
2
- //
3
- // Os comandos generate-spec/generate-plan/decompose rodam dentro de Actions
4
- // disparadas por label — a linha de comando do workflow é fixa e não tem como
5
- // receber uma flag. Por isso o force chega de duas formas equivalentes:
6
- // • label `spec-wave:force` na issue (fluxo normal, pelo board);
7
- // • flag `--force` (execução local/manual da CLI).
8
- // A label é CONSUMIDA pelo run que a leu (ver consumeForceLabel), senão ela
9
- // ficaria pendurada e forçaria silenciosamente todos os runs seguintes.
10
- import { LABEL_FORCE } from '../config.mjs';
11
- import { removeLabel } from '../api/github-rest.mjs';
12
-
13
- /**
14
- * Diz se a etapa deve ser re-executada ignorando os guards (função PURA).
15
- *
16
- * @param {object} [params]
17
- * @param {Array<string|{name: string}>} [params.labels] labels da issue
18
- * @param {boolean} [params.flag] valor da flag `--force` da CLI
19
- * @returns {boolean}
20
- */
21
- export function isForced({ labels = [], flag = false } = {}) {
22
- if (flag) return true;
23
- return (labels || [])
24
- .map(l => (typeof l === 'string' ? l : l?.name))
25
- .includes(LABEL_FORCE);
26
- }
27
-
28
- /**
29
- * Remove a label `spec-wave:force` da issue. Best-effort: nunca lança — deixar
30
- * de consumir a label é um incômodo, não um motivo para derrubar o comando.
31
- */
32
- export async function consumeForceLabel(token, owner, repo, issueNumber) {
33
- await removeLabel(token, owner, repo, issueNumber, LABEL_FORCE).catch(() => {});
34
- }