@spec-wave/cli 0.19.0 → 0.20.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.19.0",
3
+ "version": "0.20.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": {
@@ -31,7 +31,7 @@ import {
31
31
  parseCritiqueDecisions, applyCritiqueDecisions, renderRiskAcceptedComment,
32
32
  } from '../lib/critique.mjs';
33
33
  import { recordUsage } from '../lib/usage-report.mjs';
34
- import { formatDependencyLine } from '../lib/dependencies.mjs';
34
+ import { formatDependencyLine, orderStories, renderOrderComment } from '../lib/dependencies.mjs';
35
35
  import { lintLanguage } from '../lib/output-lint.mjs';
36
36
  import { slugify } from '../lib/slugify.mjs';
37
37
  import { detectIssueType } from '../lib/issue-type.mjs';
@@ -633,10 +633,57 @@ async function createStoriesFromDoc(ctx, doc) {
633
633
  posicionamento +
634
634
  formatItemsLintWarning(generatedTexts)
635
635
  ).catch(err => console.warn(`Falha ao comentar a decomposição: ${err.message}`));
636
+
637
+ await commentStoryOrder({ token, owner, repo, issueNumber, doc, createdStories });
638
+
636
639
  console.log(`Decomposição aplicada: ${created.length} stories criadas.`);
637
640
  return boardFailures;
638
641
  }
639
642
 
643
+ /**
644
+ * Publica a ordem de execução das Stories como um segundo comentário.
645
+ *
646
+ * Os dados saem do que ACABOU de ser criado — `createdStories` (índice → issue)
647
+ * e `doc.stories[i].dependsOn` (0-based, já validado pelo parser). Não consulta
648
+ * o board: reconstruir isso pela API custaria ~4 chamadas por Story para chegar
649
+ * ao que já está em memória, e a Etapa é ✅ Ready por construção — se não fosse,
650
+ * `failIfBoardIncomplete` já teria derrubado o run.
651
+ *
652
+ * BEST-EFFORT, como o comentário que o antecede: a decomposição está feita e
653
+ * paga, e falhar ao comentar não pode desfazê-la nem mascarar o que deu certo.
654
+ */
655
+ async function commentStoryOrder({ token, owner, repo, issueNumber, doc, createdStories }) {
656
+ try {
657
+ const stories = doc.stories
658
+ .map((story, i) => {
659
+ const criada = createdStories[i];
660
+ if (!criada) return null; // criação falhou no meio: não inventa a Story
661
+ return {
662
+ number: criada.number,
663
+ title: story.title,
664
+ // dependsOn vem 0-based (índice no doc) e vira número de issue.
665
+ dependsOn: (story.dependsOn || [])
666
+ .map(idx => createdStories[idx]?.number)
667
+ .filter(Boolean),
668
+ };
669
+ })
670
+ .filter(Boolean);
671
+
672
+ if (stories.length === 0) return;
673
+
674
+ const byNumber = new Map(stories.map(s => [s.number, s]));
675
+ const { order: sorted, cycle } = orderStories(
676
+ stories.map(({ number, dependsOn }) => ({ number, dependsOn }))
677
+ );
678
+
679
+ await commentOnIssue(token, owner, repo, parseInt(issueNumber, 10),
680
+ renderOrderComment({ sorted, cycle, byNumber }));
681
+ console.log(`Ordem de execução comentada: ${sorted.length} story(ies).`);
682
+ } catch (err) {
683
+ console.warn(`Falha ao comentar a ordem de execução: ${err.message}`);
684
+ }
685
+ }
686
+
640
687
  async function createTasksFromDoc(ctx, doc) {
641
688
  const { token, projectToken, owner, repo, issue, issueNumber, project, etapaField, statusField, docRel } = ctx;
642
689
  const parentNodeId = issue.node_id;
@@ -90,3 +90,52 @@ export function orderStories(stories) {
90
90
  .sort((a, b) => a - b);
91
91
  return { order, cycle };
92
92
  }
93
+
94
+ /**
95
+ * Comentário com a ordem de execução das Stories (função PURA).
96
+ *
97
+ * A decomposição já comentava a lista das Stories criadas, mas na ordem de
98
+ * criação e sem dizer o que depende de quê — para saber por onde começar, o dev
99
+ * tinha de rodar `spec-wave order` no terminal. A informação existia fora da
100
+ * issue, que é onde o time olha.
101
+ *
102
+ * Sobre a expectativa: como `normalizeDependsOn` só aceita dependências para
103
+ * trás, a ordem de criação já é topologicamente válida e `orderStories` devolve
104
+ * a mesma sequência. O ganho aqui não é reordenar — é tornar a cadeia visível,
105
+ * com os números clicáveis. Passar por `orderStories` mesmo assim mantém uma só
106
+ * definição de "ordem de execução" (a que o comando `order` usa) e dá a
107
+ * detecção de ciclo de graça.
108
+ *
109
+ * @param {object} params
110
+ * @param {number[]} params.sorted números das Stories em ordem topológica
111
+ * @param {number[]} [params.cycle] Stories que ficaram fora da ordem (ciclo)
112
+ * @param {Map<number,{title: string, dependsOn: number[]}>} params.byNumber
113
+ * @returns {string} markdown
114
+ */
115
+ export function renderOrderComment({ sorted, cycle = [], byNumber }) {
116
+ const linha = (n, i) => {
117
+ const s = byNumber.get(n);
118
+ const deps = s?.dependsOn?.length > 0
119
+ ? ` — depende de ${s.dependsOn.map(d => `#${d}`).join(', ')}`
120
+ : '';
121
+ return `${i + 1}. #${n} ${s?.title ?? ''}${deps}`.trimEnd();
122
+ };
123
+
124
+ const partes = [
125
+ '📋 **Ordem de execução das Stories**',
126
+ sorted.map(linha).join('\n'),
127
+ ];
128
+
129
+ // Espelha o aviso que o comando `order` dá no terminal. Estruturalmente o
130
+ // parser impede ciclo (dependência só para trás), mas se a validação relaxar,
131
+ // silenciar aqui esconderia Stories do comentário — e ninguém perceberia.
132
+ if (cycle.length > 0) {
133
+ partes.push(
134
+ `⚠️ **Ciclo de dependências:** ${cycle.map(n => `#${n}`).join(', ')} ` +
135
+ 'ficaram FORA da ordem acima. Corrija as linhas "Depende de" (ou as relações ' +
136
+ 'blocked_by) dessas issues.'
137
+ );
138
+ }
139
+
140
+ return partes.join('\n\n');
141
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "spec-wave",
3
3
  "displayName": "Spec Wave",
4
- "version": "0.19.0",
4
+ "version": "0.20.0",
5
5
  "description": "Fluxo spec-driven no GitHub (RFC-001): Projects v2, labels de gatilho, spec/plan gerados por Action, decomposição em duas etapas e implementação orientada a Stories/Tasks.",
6
6
  "author": {
7
7
  "name": "Astratech",