@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
|
@@ -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;
|
package/src/lib/dependencies.mjs
CHANGED
|
@@ -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.
|
|
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",
|