@spec-wave/cli 0.5.11 → 0.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spec-wave/cli",
3
- "version": "0.5.11",
3
+ "version": "0.6.0",
4
4
  "description": "Setup spec-driven GitHub workflow with Projects v2, labels, issue templates, and AI-powered Actions",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,11 +4,14 @@ import { resolveToken } from '../api/auth.mjs';
4
4
  import { getIssue, getPR, commentOnIssue } from '../api/github-rest.mjs';
5
5
  import { addProjectItem, setItemSingleSelect, getSingleSelectField, getIssueParent, listSubIssues, getItemSingleSelectValue } from '../api/github-graphql.mjs';
6
6
  import { detectIssueType } from '../lib/issue-type.mjs';
7
- import { CONFIG_FILE, STATUS_OPTIONS, STAGE_ORDER, PROGRESS_TODO } from '../config.mjs';
7
+ import { CONFIG_FILE, STATUS_OPTIONS, STAGE_ORDER, STAGE_DONE, PROGRESS_TODO, PROGRESS_DONE } from '../config.mjs';
8
8
 
9
- // Campo "Etapa" (custom) → "👀 Code Review". Campo "Status" (nativo) → "Todo".
9
+ // Ao abrir o PR: Stories/Feature Etapa "👀 Code Review" (Status "Todo");
10
+ // Tasks → Etapa "🎉 Done" (Status "Done"), pois a implementação da task terminou.
10
11
  const CODE_REVIEW_STAGE = STATUS_OPTIONS.find(s => s.name.includes('Code Review'))?.name;
12
+ const DONE_STAGE = STAGE_DONE;
11
13
  const TODO_STATUS = PROGRESS_TODO;
14
+ const DONE_STATUS = PROGRESS_DONE;
12
15
 
13
16
  // Extrai números de issues referenciadas no corpo do PR (Closes #N, Fixes #N, #N solto).
14
17
  function extractIssueNumbers(body) {
@@ -54,19 +57,20 @@ async function resolveFeatureIssue(token, owner, repo, issueNumber) {
54
57
  return null;
55
58
  }
56
59
 
57
- // Coleta a "unidade de review" de uma issue referenciada no PR. Separa a
58
- // Feature dos demais itens, porque a Feature tem regra própria (só avança
59
- // quando TODAS as suas Stories estiverem em Code Review). Retorna
60
- // { feature: {number,nodeId,title}|null, items: Map(number -> {nodeId,title}) }
61
- // onde `items` são as Stories + Tasks que andam juntas neste PR.
60
+ // Coleta a "unidade de review" de uma issue referenciada no PR, separando por
61
+ // tipo porque cada um tem destino próprio: Tasks Done, Stories → Code Review,
62
+ // Feature → Code Review (só quando todas as Stories estiverem prontas). Retorna
63
+ // { feature:{number,nodeId,title}|null, stories:Map, tasks:Map }.
62
64
  async function collectReviewUnit(token, owner, repo, issueNumber) {
63
- const items = new Map();
64
- const add = (n, nodeId, title) => { if (n && nodeId && !items.has(n)) items.set(n, { nodeId, title }); };
65
+ const stories = new Map();
66
+ const tasks = new Map();
67
+ const addStory = (n, nodeId, title) => { if (n && nodeId && !stories.has(n)) stories.set(n, { nodeId, title }); };
68
+ const addTask = (n, nodeId, title) => { if (n && nodeId && !tasks.has(n)) tasks.set(n, { nodeId, title }); };
65
69
 
66
70
  let issue;
67
- try { issue = await getIssue(token, owner, repo, issueNumber); } catch { return { feature: null, items }; }
71
+ try { issue = await getIssue(token, owner, repo, issueNumber); } catch { return { feature: null, stories, tasks }; }
68
72
  const type = detectIssueType(issue);
69
- if (type !== 'Feature' && type !== 'Story' && type !== 'Task') return { feature: null, items };
73
+ if (type !== 'Feature' && type !== 'Story' && type !== 'Task') return { feature: null, stories, tasks };
70
74
 
71
75
  const featureIssue = await resolveFeatureIssue(token, owner, repo, issueNumber);
72
76
  const feature = featureIssue
@@ -75,26 +79,26 @@ async function collectReviewUnit(token, owner, repo, issueNumber) {
75
79
 
76
80
  if (type === 'Feature') {
77
81
  // Referência direta à Feature: toda a subárvore (Stories + Tasks).
78
- const stories = await listSubIssues(token, issue.node_id).catch(() => []);
79
- for (const st of stories) {
80
- add(st.number, st.nodeId, st.title);
81
- const tasks = await listSubIssues(token, st.nodeId).catch(() => []);
82
- for (const t of tasks) add(t.number, t.nodeId, t.title);
82
+ const subs = await listSubIssues(token, issue.node_id).catch(() => []);
83
+ for (const st of subs) {
84
+ addStory(st.number, st.nodeId, st.title);
85
+ const tks = await listSubIssues(token, st.nodeId).catch(() => []);
86
+ for (const t of tks) addTask(t.number, t.nodeId, t.title);
83
87
  }
84
88
  } else if (type === 'Story') {
85
- add(issue.number, issue.node_id, issue.title);
86
- const tasks = await listSubIssues(token, issue.node_id).catch(() => []);
87
- for (const t of tasks) add(t.number, t.nodeId, t.title);
89
+ addStory(issue.number, issue.node_id, issue.title);
90
+ const tks = await listSubIssues(token, issue.node_id).catch(() => []);
91
+ for (const t of tks) addTask(t.number, t.nodeId, t.title);
88
92
  } else { // Task → inclui a Story pai e as Tasks irmãs.
89
- add(issue.number, issue.node_id, issue.title);
93
+ addTask(issue.number, issue.node_id, issue.title);
90
94
  const parent = await getIssueParent(token, issue.node_id).catch(() => null);
91
95
  if (parent && detectIssueType({ title: parent.title }) === 'Story') {
92
- add(parent.number, parent.nodeId, parent.title);
93
- const tasks = await listSubIssues(token, parent.nodeId).catch(() => []);
94
- for (const t of tasks) add(t.number, t.nodeId, t.title);
96
+ addStory(parent.number, parent.nodeId, parent.title);
97
+ const tks = await listSubIssues(token, parent.nodeId).catch(() => []);
98
+ for (const t of tks) addTask(t.number, t.nodeId, t.title);
95
99
  }
96
100
  }
97
- return { feature, items };
101
+ return { feature, stories, tasks };
98
102
  }
99
103
 
100
104
  // A Feature só avança quando TODAS as suas Stories já estiverem em Code Review
@@ -114,26 +118,25 @@ async function allStoriesReadyForReview(readToken, projToken, project, etapaFiel
114
118
  return true;
115
119
  }
116
120
 
117
- // Avança um item do board para a Etapa "👀 Code Review" e reinicia o Status
118
- // (nativo) para "Todo". Uma issue só AVANÇA: se já estiver em Code Review ou em
119
- // uma etapa posterior, não é tocada (retorna false). Retorna true se avançou.
120
- async function setCodeReview(token, project, etapaField, statusField, nodeId) {
121
+ // Avança um item do board para `targetStage` (Etapa) e define o Status para
122
+ // `targetStatus`. Uma issue só AVANÇA: se já estiver em `targetStage` ou em uma
123
+ // etapa posterior, não é tocada (retorna false). Retorna true se avançou.
124
+ async function advanceToStage(token, project, etapaField, statusField, nodeId, targetStage, targetStatus) {
121
125
  const itemId = await addProjectItem(token, project.id, nodeId);
122
126
 
123
- if (etapaField?.id && CODE_REVIEW_STAGE) {
127
+ if (etapaField?.id && targetStage) {
124
128
  // Nunca retroceder: compara a etapa atual com a de destino na ordem canônica.
125
129
  const current = await getItemSingleSelectValue(token, itemId, etapaField.id).catch(() => null);
126
130
  const curIdx = current ? STAGE_ORDER.indexOf(current) : -1;
127
- const tgtIdx = STAGE_ORDER.indexOf(CODE_REVIEW_STAGE);
131
+ const tgtIdx = STAGE_ORDER.indexOf(targetStage);
128
132
  if (curIdx !== -1 && tgtIdx !== -1 && curIdx >= tgtIdx) {
129
- return false; // já está em Code Review ou adiante — não retrocede
133
+ return false; // já está nessa etapa ou adiante — não retrocede
130
134
  }
131
- const optionId = etapaField.options?.[CODE_REVIEW_STAGE];
135
+ const optionId = etapaField.options?.[targetStage];
132
136
  if (optionId) await setItemSingleSelect(token, project.id, itemId, etapaField.id, optionId);
133
137
  }
134
- // Ao avançar de etapa, o Status reinicia em "Todo".
135
- if (statusField?.id) {
136
- const optionId = statusField.options?.[TODO_STATUS];
138
+ if (statusField?.id && targetStatus) {
139
+ const optionId = statusField.options?.[targetStatus];
137
140
  if (optionId) await setItemSingleSelect(token, project.id, itemId, statusField.id, optionId);
138
141
  }
139
142
  return true;
@@ -190,18 +193,37 @@ export async function codeReview({ prNumber }) {
190
193
  const updated = [];
191
194
  const featuresChecked = new Set();
192
195
 
193
- // Para cada issue referenciada: move a Story + Tasks para Code Review. A
194
- // Feature avança quando TODAS as suas Stories estiverem em Code Review.
196
+ // Regras ao abrir o PR: Tasks 🎉 Done (implementação concluída);
197
+ // Stories 👀 Code Review; Feature Code Review quando TODAS as suas
198
+ // Stories já estiverem em Code Review.
195
199
  for (const num of issueNums) {
196
- const { feature, items } = await collectReviewUnit(token, owner, repo, num);
200
+ const { feature, stories, tasks } = await collectReviewUnit(token, owner, repo, num);
197
201
 
198
- for (const [n, info] of items) {
202
+ // Tasks Done (Status Done).
203
+ for (const [n, info] of tasks) {
199
204
  if (seen.has(n)) continue;
200
205
  seen.add(n);
201
206
  try {
202
- const moved = await setCodeReview(projectToken, project, etapaField, statusField, info.nodeId);
207
+ const moved = await advanceToStage(projectToken, project, etapaField, statusField, info.nodeId, DONE_STAGE, DONE_STATUS);
203
208
  if (moved) {
204
- updated.push(`#${n} ${info.title}`);
209
+ updated.push(`#${n} ${info.title} → ${DONE_STAGE}`);
210
+ console.log(`#${n} → "${DONE_STAGE}" / Status "${DONE_STATUS}".`);
211
+ } else {
212
+ console.log(`#${n} já está em "${DONE_STAGE}" ou etapa posterior — mantido (não retrocede).`);
213
+ }
214
+ } catch (err) {
215
+ console.warn(`Falha ao atualizar #${n}: ${err.message}`);
216
+ }
217
+ }
218
+
219
+ // Stories → Code Review (Status Todo).
220
+ for (const [n, info] of stories) {
221
+ if (seen.has(n)) continue;
222
+ seen.add(n);
223
+ try {
224
+ const moved = await advanceToStage(projectToken, project, etapaField, statusField, info.nodeId, CODE_REVIEW_STAGE, TODO_STATUS);
225
+ if (moved) {
226
+ updated.push(`#${n} ${info.title} → ${CODE_REVIEW_STAGE}`);
205
227
  console.log(`#${n} → "${CODE_REVIEW_STAGE}" / Status "${TODO_STATUS}".`);
206
228
  } else {
207
229
  console.log(`#${n} já está em "${CODE_REVIEW_STAGE}" ou etapa posterior — mantido (não retrocede).`);
@@ -220,9 +242,9 @@ export async function codeReview({ prNumber }) {
220
242
  console.log(`Feature #${feature.number} mantida em desenvolvimento — ainda há Stories pendentes (fora de "${CODE_REVIEW_STAGE}").`);
221
243
  } else if (!seen.has(feature.number)) {
222
244
  seen.add(feature.number);
223
- const moved = await setCodeReview(projectToken, project, etapaField, statusField, feature.nodeId);
245
+ const moved = await advanceToStage(projectToken, project, etapaField, statusField, feature.nodeId, CODE_REVIEW_STAGE, TODO_STATUS);
224
246
  if (moved) {
225
- updated.push(`#${feature.number} ${feature.title} (Feature)`);
247
+ updated.push(`#${feature.number} ${feature.title} (Feature) → ${CODE_REVIEW_STAGE}`);
226
248
  console.log(`Feature #${feature.number} → "${CODE_REVIEW_STAGE}" (todas as Stories concluídas).`);
227
249
  } else {
228
250
  console.log(`Feature #${feature.number} já está em "${CODE_REVIEW_STAGE}" ou etapa posterior.`);
@@ -238,10 +260,10 @@ export async function codeReview({ prNumber }) {
238
260
  await commentOnIssue(
239
261
  token, owner, repo, parseInt(prNumber, 10),
240
262
  `🔍 **Code Review iniciado**\n\n` +
241
- `Movidos para **${CODE_REVIEW_STAGE}** (a Feature só avança quando todas as suas Stories concluírem):\n\n` +
263
+ `Board atualizado (Tasks → **${DONE_STAGE}**; Story → **${CODE_REVIEW_STAGE}**; Feature só avança quando todas as Stories concluírem):\n\n` +
242
264
  updated.map(f => `- ${f}`).join('\n')
243
265
  ).catch(() => {});
244
266
  }
245
267
 
246
- console.log(`code-review: ${updated.length} item(ns) movido(s) para "${CODE_REVIEW_STAGE}".`);
268
+ console.log(`code-review: ${updated.length} item(ns) atualizado(s) no board.`);
247
269
  }
@@ -5,7 +5,7 @@ import { execSync } from 'node:child_process';
5
5
  import path from 'node:path';
6
6
  import { resolveToken } from '../api/auth.mjs';
7
7
  import {
8
- CONFIG_FILE, STAGE_DEVELOPMENT, STAGE_CODE_REVIEW,
8
+ CONFIG_FILE, STAGE_DEVELOPMENT, STAGE_CODE_REVIEW, STAGE_DONE,
9
9
  PROGRESS_TODO, PROGRESS_IN_PROGRESS, PROGRESS_DONE,
10
10
  } from '../config.mjs';
11
11
  import { getIssue } from '../api/github-rest.mjs';
@@ -57,38 +57,38 @@ function buildContext({ type, issue, tasks, feature, siblingStories = [], spec,
57
57
  }
58
58
 
59
59
  // Modelo do board: "Etapa" (coluna do kanban) = DIREÇÃO, só avança; "Status"
60
- // (Todo/In Progress/Done) = PROGRESSO dentro da etapa, reinicia a cada avanço.
61
- // O desenvolvimento acontece na Etapa 🚧 Desenvolvimento; o progresso por task
62
- // é registrado no STATUS. Ao final, a Etapa avança para Code Review.
60
+ // (Todo/In Progress/Done) = PROGRESSO dentro da etapa. O desenvolvimento de
61
+ // cada Task acontece na Etapa Desenvolvimento (Status In Progress); ao concluir,
62
+ // a Task avança para a Etapa Done (Status Done). A Story avança para Code Review.
63
63
  lines.push('');
64
64
  lines.push('## Instruções de execução (uma task por vez, sequencial)');
65
65
  lines.push('');
66
66
  lines.push(
67
67
  'Há **dois campos** no board com papéis diferentes — não os confunda:\n' +
68
- `- **Etapa** (Backlog → … → ${STAGE_DEVELOPMENT} → ${STAGE_CODE_REVIEW} → …): a DIREÇÃO no kanban. Uma issue só **avança**, **nunca** volta para uma etapa anterior.\n` +
69
- `- **Status** (${PROGRESS_TODO} → ${PROGRESS_IN_PROGRESS} → ${PROGRESS_DONE}): o **progresso dentro da etapa atual**. Ao avançar de etapa, o Status **reinicia em ${PROGRESS_TODO}**.`
68
+ `- **Etapa** (Backlog → … → ${STAGE_DEVELOPMENT} → ${STAGE_CODE_REVIEW} → … → ${STAGE_DONE}): a DIREÇÃO no kanban. Uma issue só **avança**, **nunca** volta para uma etapa anterior.\n` +
69
+ `- **Status** (${PROGRESS_TODO} → ${PROGRESS_IN_PROGRESS} → ${PROGRESS_DONE}): o **progresso dentro da etapa atual**. Ao avançar de etapa, o Status reinicia em ${PROGRESS_TODO} — exceto ao chegar na Etapa ${STAGE_DONE}, onde o Status fica **${PROGRESS_DONE}**.`
70
70
  );
71
71
  lines.push('');
72
72
  if (type === 'Story') {
73
73
  lines.push(
74
74
  `Nesta fase, a Story #${issue.number} e suas Tasks estão na Etapa **${STAGE_DEVELOPMENT}**. ` +
75
- 'Durante a implementação, mexa **apenas no Status** das Tasks — **não** mude a Etapa delas.'
75
+ `Durante o desenvolvimento de cada Task, mexa no **Status**; ao **concluir** a Task, ela ` +
76
+ `**avança para a Etapa ${STAGE_DONE}** (com Status ${PROGRESS_DONE}).`
76
77
  );
77
78
  lines.push('');
78
79
  lines.push(`1. Garanta que a Story #${issue.number} está na Etapa **${STAGE_DEVELOPMENT}** com Status **${PROGRESS_IN_PROGRESS}**, e que as Tasks estão nessa Etapa com Status **${PROGRESS_TODO}**.`);
79
80
  lines.push(`2. Implemente as ${tasks.length} task(s) **uma de cada vez, na ordem abaixo**. É PROIBIDO ter mais de uma Task com Status **${PROGRESS_IN_PROGRESS}** ao mesmo tempo. Para **cada Task**, na ordem:`);
80
81
  lines.push(` 1. **Ao começar:** Status da Task → **${PROGRESS_IN_PROGRESS}** (a Etapa continua ${STAGE_DEVELOPMENT}).`);
81
82
  lines.push(' 2. **Implemente** a Task por completo.');
82
- lines.push(` 3. **Ao concluir:** Status da Task **${PROGRESS_DONE}** (ainda na Etapa ${STAGE_DEVELOPMENT}).`);
83
+ lines.push(` 3. **Ao concluir:** **avance a Task para a Etapa ${STAGE_DONE}** com Status **${PROGRESS_DONE}**.`);
83
84
  lines.push(' 4. Só então avance para a próxima Task.');
84
85
  lines.push('');
85
- lines.push(`3. **Ao concluir TODA a Story** (todas as Tasks com Status ${PROGRESS_DONE}):`);
86
+ lines.push(`3. **Ao concluir TODA a Story** (todas as Tasks na Etapa ${STAGE_DONE}):`);
86
87
  lines.push(' 1. Faça o **commit** de todas as mudanças da implementação.');
87
88
  lines.push(` 2. Abra o **Pull Request** da Story #${issue.number}.`);
88
89
  lines.push(
89
- ` 3. **Avance a Etapa da Story #${issue.number} e de todas as suas ${tasks.length} Task(s) ` +
90
- `(${tasks.map(t => `#${t.number}`).join(', ')}) para ${STAGE_CODE_REVIEW}**, reiniciando o Status ` +
91
- `de cada uma para ${PROGRESS_TODO}. (Story e Tasks andam juntas.)`
90
+ ` 3. **Avance a Etapa da Story #${issue.number} para ${STAGE_CODE_REVIEW}** ` +
91
+ `(reinicie o Status para ${PROGRESS_TODO}). As Tasks estão em ${STAGE_DONE}.`
92
92
  );
93
93
  // A Feature só avança quando TODAS as suas Stories estiverem implementadas.
94
94
  lines.push(` 4. **A Feature${feature ? ` #${feature.number}` : ' (issue pai da Story)'} só avança para ${STAGE_CODE_REVIEW} quando TODAS as suas Stories estiverem implementadas:**`);
@@ -100,21 +100,16 @@ function buildContext({ type, issue, tasks, feature, siblingStories = [], spec,
100
100
  lines.push(` - Se **qualquer** Story ainda estiver pendente (antes de ${STAGE_CODE_REVIEW}), **NÃO mova a Feature** — deixe-a em ${STAGE_DEVELOPMENT} até a última Story ser concluída.`);
101
101
  }
102
102
  } else {
103
- lines.push(
104
- `Esta Task #${issue.number} está na Etapa **${STAGE_DEVELOPMENT}**. Acompanhe o progresso pelo ` +
105
- '**Status** — **não** mude a Etapa aqui:'
106
- );
103
+ lines.push(`Esta Task #${issue.number} está na Etapa **${STAGE_DEVELOPMENT}**:`);
107
104
  lines.push('');
108
- lines.push(`1. **Ao começar:** Status da Task #${issue.number} → **${PROGRESS_IN_PROGRESS}**.`);
105
+ lines.push(`1. **Ao começar:** Status da Task #${issue.number} → **${PROGRESS_IN_PROGRESS}** (Etapa continua ${STAGE_DEVELOPMENT}).`);
109
106
  lines.push('2. **Implemente** a Task por completo.');
110
- lines.push(`3. **Ao concluir:** Status da Task #${issue.number} **${PROGRESS_DONE}**.`);
111
- lines.push('');
112
- lines.push(`> A Etapa avança para **${STAGE_CODE_REVIEW}** (Status reinicia em ${PROGRESS_TODO}) junto com a Story/Feature quando o PR é aberto — nunca volta para uma etapa anterior.`);
107
+ lines.push(`3. **Ao concluir:** **avance a Task #${issue.number} para a Etapa ${STAGE_DONE}** com Status **${PROGRESS_DONE}**.`);
113
108
  }
114
109
  lines.push('');
115
110
  lines.push(
116
111
  `> **Regra do board:** a **Etapa** só avança (nunca retrocede); o **Status** (${PROGRESS_TODO}/${PROGRESS_IN_PROGRESS}/${PROGRESS_DONE}) ` +
117
- 'só mede o progresso dentro da etapa atual e reinicia a cada avanço de etapa.'
112
+ `mede o progresso dentro da etapa atual e reinicia a cada avanço (na Etapa ${STAGE_DONE}, o Status fica ${PROGRESS_DONE}).`
118
113
  );
119
114
 
120
115
  lines.push('');
package/src/config.mjs CHANGED
@@ -60,6 +60,7 @@ export const STATUS_OPTIONS = [
60
60
  // Etapas (campo Etapa) referenciadas pelo fluxo de implementação.
61
61
  export const STAGE_DEVELOPMENT = STATUS_OPTIONS.find(s => s.name.includes('Desenvolvimento')).name;
62
62
  export const STAGE_CODE_REVIEW = STATUS_OPTIONS.find(s => s.name.includes('Code Review')).name;
63
+ export const STAGE_DONE = STATUS_OPTIONS.find(s => s.name.includes('Done')).name;
63
64
  // Ordem canônica das etapas — usada para garantir que uma issue só AVANÇA.
64
65
  export const STAGE_ORDER = STATUS_OPTIONS.map(s => s.name);
65
66
 
@@ -166,7 +166,7 @@ Mesmas flags do `issue` (exceto `--type`, fixo em `feature`). Mantido para o flu
166
166
  | `--feature-dir <path>` | string | Caminho `docs/features/<slug>` para anexar `spec.md`/`plan.md` como contexto (sobrescreve a resolução automática). |
167
167
  | `--dry-run` | flag | Monta o contexto e imprime o comando do spec-kit **sem executar**. |
168
168
 
169
- > Diferente dos quatro acima, `implement` roda **localmente** (lê `.spec-wave.json`, como `issue`), não por Action. Detecta o tipo da issue: **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 instruções para o agente implementar as Tasks **sequencialmente, uma por vez**, usando o campo **Status** (Todo → In Progress Done) para o progresso *dentro* da Etapa 🚧 Desenvolvimento sem trocar a Etapa das tasks (nunca duas com Status "In Progress" ao mesmo tempo). **Ao concluir toda a Story**: fazer o commit, abrir o PR e **avançar a Etapa** da **Story e de todas as suas Tasks** para **👀 Code Review** (Status → Todo). 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.
169
+ > Diferente dos quatro acima, `implement` roda **localmente** (lê `.spec-wave.json`, como `issue`), não por Action. Detecta o tipo da issue: **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 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.
170
170
 
171
171
  ---
172
172
 
@@ -396,7 +396,7 @@ Aciona o spec-kit para implementar uma **Story** (todas as suas Tasks) ou uma **
396
396
  npx @spec-wave/cli implement <número> --dry-run
397
397
  ```
398
398
  3. Mostre ao usuário o contexto montado em `.spec-wave/implement-<número>.md` e o comando. Esse arquivo contém as **instruções de execução sequencial**: implemente as Tasks **uma por vez** — mova a task para **🚧 Desenvolvimento** só ao iniciá-la e para **🎉 Done** ao concluí-la, antes de passar para a próxima. **Nunca** coloque várias tasks em "in progress" ao mesmo tempo.
399
- 4. **Se você (agente) for implementar diretamente** (sem `specKit.command`): siga o contexto task por task. Para cada task, use o campo **Status** (In Progress ao começar → Done ao concluir) *dentro* da Etapa 🚧 Desenvolvimento não troque a Etapa da task. Atualize os campos via `gh`. **Ao concluir toda a Story**: faça o commit, abra o PR e **avance a Etapa** da **Story e de todas as suas Tasks** para **👀 Code Review** (Status → Todo). A **Feature só avança** quando **TODAS as suas Stories** já estiverem em Code Review — se houver Story pendente, deixe a Feature em 🚧 Desenvolvimento. Lembre: Etapa só avança (nunca volta); Status é o progresso dentro da etapa.
399
+ 4. **Se você (agente) for implementar diretamente** (sem `specKit.command`): siga o contexto task por task. Para cada task: Status In Progress *dentro* da Etapa 🚧 Desenvolvimento e, **ao concluir, avance a task para a Etapa 🎉 Done com Status Done**. Atualize os campos via `gh`. **Ao concluir toda a Story**: faça o commit, abra o PR e **avance a Etapa da Story para 👀 Code Review** (Status → Todo) — as Tasks já estão em 🎉 Done. A **Feature só avança** quando **TODAS as suas Stories** já estiverem em Code Review — se houver Story pendente, deixe a Feature em 🚧 Desenvolvimento. Lembre: Etapa só avança (nunca volta); Status é o progresso dentro da etapa.
400
400
  5. Se o usuário aprovar e o spec-kit estiver configurado, rode sem `--dry-run`:
401
401
  ```bash
402
402
  npx @spec-wave/cli implement <número>
@@ -404,7 +404,7 @@ Aciona o spec-kit para implementar uma **Story** (todas as suas Tasks) ou uma **
404
404
  - Se o spec-kit **não** estiver configurado, o comando só monta o contexto e mostra como configurar (`specKit.command` / `SPEC_WAVE_IMPLEMENT_CMD`). Ajude o usuário a definir o template (placeholders: `{tasksFile} {specFile} {planFile} {issue} {type} {title}`).
405
405
  - Use `--feature-dir docs/features/<slug>` se a resolução automática da Feature falhar (a skill avisa com warning) e você quiser anexar `spec.md`/`plan.md` como contexto.
406
406
  6. Se a issue **não** for Story nem Task (ex.: Feature, Bug), o comando recusa — oriente o usuário: Features se decompõem (`/spec-wave decompose`); implemente as Stories/Tasks resultantes.
407
- 7. Ao final (Story + Tasks em **👀 Code Review**; a Feature só vai para Code Review quando a última Story concluir): confirme o resultado com o usuário e oriente a revisão do PR.
407
+ 7. Ao final (Tasks em **🎉 Done**, Story em **👀 Code Review**; a Feature só vai para Code Review quando a última Story concluir): confirme o resultado com o usuário e oriente a revisão do PR.
408
408
 
409
409
  ---
410
410