@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.
- package/README.md +39 -27
- package/bin/spec-wave.mjs +14 -4
- package/package.json +1 -1
- package/src/api/github-graphql.mjs +0 -4
- package/src/api/github-rest.mjs +0 -13
- package/src/commands/code-review.mjs +5 -8
- package/src/commands/decompose.mjs +410 -251
- package/src/commands/dev-agent.mjs +3 -2
- package/src/commands/doctor.mjs +239 -9
- package/src/commands/generate-plan.mjs +111 -51
- package/src/commands/generate-spec.mjs +20 -22
- package/src/commands/implement.mjs +46 -24
- package/src/commands/info.mjs +4 -3
- package/src/commands/issue.mjs +4 -4
- package/src/commands/move.mjs +162 -0
- package/src/commands/order.mjs +1 -12
- package/src/commands/qa.mjs +5 -8
- package/src/commands/refresh.mjs +4 -3
- package/src/commands/story.mjs +1 -12
- package/src/commands/task.mjs +1 -11
- package/src/commands/update.mjs +43 -19
- package/src/commands/validate.mjs +47 -35
- package/src/config.mjs +40 -6
- package/src/lib/board.mjs +88 -26
- package/src/lib/claude.mjs +315 -70
- package/src/lib/critique.mjs +391 -91
- package/src/lib/decomposition-doc.mjs +451 -0
- package/src/lib/implement-board.mjs +14 -1
- package/src/lib/project-root.mjs +93 -0
- package/src/lib/templates.mjs +53 -0
- package/src/setup/files.mjs +3 -10
- package/src/templates/skill/SKILL.md +137 -61
- package/src/templates/workflows/code-review.yml +1 -1
- package/src/templates/workflows/decompose.yml +20 -6
- package/src/templates/workflows/generate-plan.yml +1 -1
- package/src/templates/workflows/generate-spec.yml +1 -1
- package/src/templates/workflows/qa.yml +1 -1
- package/src/templates/workflows/validate.yml +1 -1
- package/src/lib/feature-docs.mjs +0 -89
- 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
|
|
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:
|
|
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**
|
|
109
|
-
|
|
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
|
-
| `--
|
|
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
|
|
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,
|
|
250
|
+
## Crítica adversarial, decomposição em duas etapas e dependências
|
|
244
251
|
|
|
245
252
|
### Crítica adversarial (comentário 🔎)
|
|
246
253
|
|
|
247
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
288
|
+
O arquivo fica em **`docs/features/<slug>/decomposition.md`** (Feature) ou **`docs/rfcs/<slug>/decomposition.md`** (RFC). Formato:
|
|
266
289
|
|
|
267
|
-
|
|
290
|
+
```markdown
|
|
291
|
+
# Decomposição — [FEATURE] Cadastro de Pedidos
|
|
292
|
+
<!-- spec-wave:decomposition v1 issue=360 kind=stories -->
|
|
268
293
|
|
|
269
|
-
|
|
294
|
+
## Story 1 — visualizar meus pedidos
|
|
270
295
|
|
|
271
|
-
|
|
296
|
+
**User story:** Como cliente, quero visualizar meus pedidos, para acompanhar entregas
|
|
297
|
+
**Depende de:** —
|
|
272
298
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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écnico — mova 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.
|
|
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 Feature — mova 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
|
|
535
|
-
5. Se passar, oriente: "Feature validada! Mova o card para **✅ Ready** e use `/spec-wave decompose <número>` para gerar
|
|
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
|
|
542
|
-
- **Feature** →
|
|
543
|
-
- **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.
|
|
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
|
-
|
|
554
|
-
|
|
555
|
-
|
|
614
|
+
Informe: "Rascunho iniciado — vai 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
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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 (3º).
|
|
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@
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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 }}
|
package/src/lib/feature-docs.mjs
DELETED
|
@@ -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
|
-
}
|