@creative-ia/cortex 1.0.5
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 +41 -0
- package/dist/config/cloud-proxy.d.ts +15 -0
- package/dist/config/cloud-proxy.js +63 -0
- package/dist/config/cloudwatch-store.d.ts +13 -0
- package/dist/config/cloudwatch-store.js +66 -0
- package/dist/config/license.d.ts +29 -0
- package/dist/config/license.js +165 -0
- package/dist/config/ssm-store.d.ts +2 -0
- package/dist/config/ssm-store.js +38 -0
- package/dist/config/telemetry.d.ts +17 -0
- package/dist/config/telemetry.js +93 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +460 -0
- package/dist/knowledge/dynamo-store.d.ts +17 -0
- package/dist/knowledge/dynamo-store.js +85 -0
- package/dist/knowledge/embeddings.d.ts +2 -0
- package/dist/knowledge/embeddings.js +36 -0
- package/dist/knowledge/loader.d.ts +8 -0
- package/dist/knowledge/loader.js +57 -0
- package/dist/lambda-package.zip +0 -0
- package/dist/lambda.d.ts +22 -0
- package/dist/lambda.js +496 -0
- package/dist/package.json +1 -0
- package/dist/tools/advance-process.d.ts +7 -0
- package/dist/tools/advance-process.js +128 -0
- package/dist/tools/analyze-code.d.ts +7 -0
- package/dist/tools/analyze-code.js +131 -0
- package/dist/tools/analyze-docs.d.ts +8 -0
- package/dist/tools/analyze-docs.js +147 -0
- package/dist/tools/config-registry.d.ts +3 -0
- package/dist/tools/config-registry.js +20 -0
- package/dist/tools/create-process.d.ts +6 -0
- package/dist/tools/create-process.js +257 -0
- package/dist/tools/decompose-epic.d.ts +7 -0
- package/dist/tools/decompose-epic.js +603 -0
- package/dist/tools/diagrams.d.ts +51 -0
- package/dist/tools/diagrams.js +304 -0
- package/dist/tools/generate-report.d.ts +9 -0
- package/dist/tools/generate-report.js +891 -0
- package/dist/tools/generate-wiki.d.ts +10 -0
- package/dist/tools/generate-wiki.js +700 -0
- package/dist/tools/get-architecture.d.ts +6 -0
- package/dist/tools/get-architecture.js +78 -0
- package/dist/tools/get-code-standards.d.ts +7 -0
- package/dist/tools/get-code-standards.js +52 -0
- package/dist/tools/init-process.d.ts +7 -0
- package/dist/tools/init-process.js +82 -0
- package/dist/tools/knowledge-crud.d.ts +26 -0
- package/dist/tools/knowledge-crud.js +142 -0
- package/dist/tools/logo-base64.d.ts +1 -0
- package/dist/tools/logo-base64.js +1 -0
- package/dist/tools/logs-query.d.ts +15 -0
- package/dist/tools/logs-query.js +46 -0
- package/dist/tools/reverse-engineer.d.ts +13 -0
- package/dist/tools/reverse-engineer.js +956 -0
- package/dist/tools/semantic-search.d.ts +7 -0
- package/dist/tools/semantic-search.js +68 -0
- package/dist/tools/update-process.d.ts +17 -0
- package/dist/tools/update-process.js +195 -0
- package/dist/tools/validate-idea.d.ts +7 -0
- package/dist/tools/validate-idea.js +339 -0
- package/dist/tools/validate-process.d.ts +6 -0
- package/dist/tools/validate-process.js +102 -0
- package/package.json +31 -0
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool: decompose_epic
|
|
3
|
+
* Recebe um EP-XX do markdown de épicos e gera a decomposição completa:
|
|
4
|
+
* EPIC-XXX.md + FEAT-XXX.md[] + US-XXX.md[] + TASK-XXX.md[]
|
|
5
|
+
*
|
|
6
|
+
* Lê o markdown de épicos do processo, parseia o EP solicitado,
|
|
7
|
+
* e gera os artefatos seguindo o padrão do processo 9760b430.
|
|
8
|
+
*/
|
|
9
|
+
import { writeFile, mkdir, readFile } from "node:fs/promises";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { existsSync } from "node:fs";
|
|
12
|
+
// ============================================================
|
|
13
|
+
// EPIC PARSER — reads EP-XX from epicos markdown
|
|
14
|
+
// ============================================================
|
|
15
|
+
function parseEpicFromMarkdown(content, epicId) {
|
|
16
|
+
const sections = content.split(/\n## /);
|
|
17
|
+
const normalizedId = epicId.replace("EP-", "").padStart(2, "0");
|
|
18
|
+
const prefix = `EP-${normalizedId}`;
|
|
19
|
+
const epicSection = sections.find((s) => s.trim().startsWith(prefix));
|
|
20
|
+
if (!epicSection)
|
|
21
|
+
return null;
|
|
22
|
+
const lines = epicSection.split("\n");
|
|
23
|
+
const titleLine = lines[0] || "";
|
|
24
|
+
const titleMatch = titleLine.match(/EP-\d{2}\s*[—-]\s*(.+)/);
|
|
25
|
+
const title = titleMatch ? titleMatch[1].trim() : titleLine.trim();
|
|
26
|
+
let objective = "";
|
|
27
|
+
let scope = [];
|
|
28
|
+
let criteria = [];
|
|
29
|
+
let dependencies = [];
|
|
30
|
+
let squads = [];
|
|
31
|
+
let size = "M";
|
|
32
|
+
let currentBlock = "";
|
|
33
|
+
for (const line of lines) {
|
|
34
|
+
const trimmed = line.trim();
|
|
35
|
+
if (!trimmed || trimmed === "---")
|
|
36
|
+
continue;
|
|
37
|
+
if (trimmed.startsWith("**Objetivo:**")) {
|
|
38
|
+
objective = trimmed.replace("**Objetivo:**", "").trim();
|
|
39
|
+
currentBlock = "";
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (trimmed.startsWith("**Escopo:**")) {
|
|
43
|
+
currentBlock = "scope";
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (trimmed.startsWith("**Critérios de aceite:**") || trimmed.startsWith("**Criterios de aceite:**")) {
|
|
47
|
+
currentBlock = "criteria";
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (trimmed.match(/^\*\*Depend[eê]ncias:\*\*/)) {
|
|
51
|
+
const val = trimmed.replace(/\*\*Depend[eê]ncias:\*\*/, "").trim();
|
|
52
|
+
if (val && !val.includes("Nenhuma")) {
|
|
53
|
+
dependencies = val.split(",").map((d) => d.trim()).filter(Boolean);
|
|
54
|
+
}
|
|
55
|
+
currentBlock = "";
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (trimmed.startsWith("**Squads:**")) {
|
|
59
|
+
squads = trimmed.replace("**Squads:**", "").trim().split("+").map((s) => s.trim()).filter(Boolean);
|
|
60
|
+
currentBlock = "";
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (trimmed.startsWith("**Estimativa:**")) {
|
|
64
|
+
size = trimmed.includes("L") ? "L" : "M";
|
|
65
|
+
currentBlock = "";
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (trimmed.startsWith("- ") && currentBlock === "scope") {
|
|
69
|
+
scope.push(trimmed.slice(2));
|
|
70
|
+
}
|
|
71
|
+
else if ((trimmed.startsWith("- [ ]") || trimmed.startsWith("- [x]")) && currentBlock === "criteria") {
|
|
72
|
+
criteria.push(trimmed.replace(/^- \[.\]\s*/, ""));
|
|
73
|
+
}
|
|
74
|
+
else if (trimmed.startsWith("- ") && currentBlock === "criteria") {
|
|
75
|
+
criteria.push(trimmed.slice(2));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { id: prefix, title, objective, scope, criteria, dependencies, squads, size };
|
|
79
|
+
}
|
|
80
|
+
// ============================================================
|
|
81
|
+
// DECOMPOSITION ENGINE
|
|
82
|
+
// ============================================================
|
|
83
|
+
function decomposeToFeatures(epic, startFeatIdx) {
|
|
84
|
+
const features = [];
|
|
85
|
+
// Group scope items into logical features (2-4 scope items per feature)
|
|
86
|
+
const scopeGroups = [];
|
|
87
|
+
const chunkSize = Math.max(2, Math.ceil(epic.scope.length / Math.ceil(epic.scope.length / 3)));
|
|
88
|
+
for (let i = 0; i < epic.scope.length; i += chunkSize) {
|
|
89
|
+
scopeGroups.push(epic.scope.slice(i, i + chunkSize));
|
|
90
|
+
}
|
|
91
|
+
scopeGroups.forEach((group, idx) => {
|
|
92
|
+
const featNum = (startFeatIdx + idx).toString().padStart(3, "0");
|
|
93
|
+
const featId = `FEAT-${featNum}`;
|
|
94
|
+
const title = inferFeatureTitle(group, epic.title);
|
|
95
|
+
const description = `Implementar ${group.join(", ").toLowerCase()} como parte do épico ${epic.id} (${epic.title}).`;
|
|
96
|
+
// Generate criteria from scope items
|
|
97
|
+
const criteria = group.map((s) => `Funcionalidade "${s}" implementada e testada`);
|
|
98
|
+
features.push({
|
|
99
|
+
id: featId,
|
|
100
|
+
epicId: epic.id,
|
|
101
|
+
title,
|
|
102
|
+
description,
|
|
103
|
+
userStories: [], // filled later
|
|
104
|
+
criteria,
|
|
105
|
+
priority: idx === 0 ? "Alta" : "Media",
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
return features;
|
|
109
|
+
}
|
|
110
|
+
function inferFeatureTitle(scopeItems, epicTitle) {
|
|
111
|
+
// Try to find a common theme in the scope items
|
|
112
|
+
const first = scopeItems[0] || epicTitle;
|
|
113
|
+
if (scopeItems.length === 1)
|
|
114
|
+
return first;
|
|
115
|
+
// Use the first item as the main title, simplified
|
|
116
|
+
const simplified = first
|
|
117
|
+
.replace(/\(.*?\)/g, "")
|
|
118
|
+
.replace(/\s+/g, " ")
|
|
119
|
+
.trim();
|
|
120
|
+
return simplified.length > 60 ? simplified.slice(0, 57) + "..." : simplified;
|
|
121
|
+
}
|
|
122
|
+
function decomposeToUserStories(feature, epic, startUsIdx) {
|
|
123
|
+
const stories = [];
|
|
124
|
+
// Each criterion becomes a user story
|
|
125
|
+
feature.criteria.forEach((criterion, idx) => {
|
|
126
|
+
const usNum = (startUsIdx + idx).toString().padStart(3, "0");
|
|
127
|
+
const usId = `US-${usNum}`;
|
|
128
|
+
// Determine persona based on content
|
|
129
|
+
const persona = inferPersona(criterion, epic);
|
|
130
|
+
stories.push({
|
|
131
|
+
id: usId,
|
|
132
|
+
featId: feature.id,
|
|
133
|
+
epicId: epic.id,
|
|
134
|
+
title: criterion.replace(/^Funcionalidade "/, "").replace(/" implementada.*/, ""),
|
|
135
|
+
asA: persona.role,
|
|
136
|
+
iWant: criterion.toLowerCase(),
|
|
137
|
+
soThat: persona.benefit,
|
|
138
|
+
criteria: generateBDDCriteria(criterion),
|
|
139
|
+
tasks: [], // filled later
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
return stories;
|
|
143
|
+
}
|
|
144
|
+
function inferPersona(criterion, epic) {
|
|
145
|
+
const lower = criterion.toLowerCase();
|
|
146
|
+
if (lower.includes("api") || lower.includes("integra") || lower.includes("backend"))
|
|
147
|
+
return { role: "desenvolvedor backend", benefit: "a integração funcione de forma confiável" };
|
|
148
|
+
if (lower.includes("tela") || lower.includes("app") || lower.includes("mobile") || lower.includes("web"))
|
|
149
|
+
return { role: "usuário do aplicativo", benefit: "eu possa realizar a operação de forma intuitiva" };
|
|
150
|
+
if (lower.includes("monitor") || lower.includes("log") || lower.includes("alert") || lower.includes("dashboard"))
|
|
151
|
+
return { role: "engenheiro de plataforma", benefit: "eu possa monitorar e diagnosticar problemas rapidamente" };
|
|
152
|
+
if (lower.includes("segur") || lower.includes("certific") || lower.includes("auth"))
|
|
153
|
+
return { role: "engenheiro de segurança", benefit: "a comunicação seja segura e auditável" };
|
|
154
|
+
return { role: "usuário do sistema", benefit: "a funcionalidade atenda aos requisitos de negócio" };
|
|
155
|
+
}
|
|
156
|
+
function generateBDDCriteria(criterion) {
|
|
157
|
+
return [
|
|
158
|
+
`Dado que o sistema está configurado corretamente`,
|
|
159
|
+
`Quando a funcionalidade "${criterion}" é executada`,
|
|
160
|
+
`Então o resultado esperado é obtido sem erros`,
|
|
161
|
+
`E logs de auditoria são gerados`,
|
|
162
|
+
];
|
|
163
|
+
}
|
|
164
|
+
function decomposeToTasks(story, startTaskIdx) {
|
|
165
|
+
const tasks = [];
|
|
166
|
+
// Dev task
|
|
167
|
+
const devNum = (startTaskIdx).toString().padStart(3, "0");
|
|
168
|
+
tasks.push({
|
|
169
|
+
id: `TASK-${devNum}`,
|
|
170
|
+
usId: story.id,
|
|
171
|
+
featId: story.featId,
|
|
172
|
+
title: `Implementar ${story.title}`,
|
|
173
|
+
type: "dev",
|
|
174
|
+
instructions: [
|
|
175
|
+
`Criar/atualizar módulos conforme especificação da ${story.id}`,
|
|
176
|
+
`Implementar lógica de negócio conforme critérios de aceite`,
|
|
177
|
+
`Integrar com APIs/serviços necessários`,
|
|
178
|
+
`Implementar tratamento de erros e logging estruturado`,
|
|
179
|
+
],
|
|
180
|
+
files: [],
|
|
181
|
+
estimate: "4h",
|
|
182
|
+
});
|
|
183
|
+
// QA task
|
|
184
|
+
const qaNum = (startTaskIdx + 1).toString().padStart(3, "0");
|
|
185
|
+
tasks.push({
|
|
186
|
+
id: `TASK-${qaNum}`,
|
|
187
|
+
usId: story.id,
|
|
188
|
+
featId: story.featId,
|
|
189
|
+
title: `Testes da ${story.id} — ${story.title}`,
|
|
190
|
+
type: "qa",
|
|
191
|
+
instructions: [
|
|
192
|
+
`Criar testes unitários com cobertura >= 80%`,
|
|
193
|
+
`Criar testes de integração para fluxos principais`,
|
|
194
|
+
`Validar critérios de aceite BDD`,
|
|
195
|
+
`Verificar tratamento de erros e edge cases`,
|
|
196
|
+
],
|
|
197
|
+
files: [],
|
|
198
|
+
estimate: "3h",
|
|
199
|
+
});
|
|
200
|
+
return tasks;
|
|
201
|
+
}
|
|
202
|
+
// ============================================================
|
|
203
|
+
// MARKDOWN GENERATORS
|
|
204
|
+
// ============================================================
|
|
205
|
+
function generateEpicMd(epic, features, processId) {
|
|
206
|
+
const epicNum = epic.id.replace("EP-", "").padStart(3, "0");
|
|
207
|
+
const now = new Date().toISOString().split("T")[0];
|
|
208
|
+
const featTable = features.map((f) => `| ${f.id} | ${f.title} | ${f.priority} | backlog |`).join("\n");
|
|
209
|
+
const criteriaList = epic.criteria.map((c) => `- [ ] ${c}`).join("\n");
|
|
210
|
+
const scopeList = epic.scope.map((s) => `- ${s}`).join("\n");
|
|
211
|
+
const depsText = epic.dependencies.length > 0 ? epic.dependencies.join(", ") : "Nenhuma (épico fundacional)";
|
|
212
|
+
const squadsText = epic.squads.join(" + ");
|
|
213
|
+
return `# EPIC-${epicNum}: ${epic.title}
|
|
214
|
+
|
|
215
|
+
> Processo: ${processId}
|
|
216
|
+
> Autor: Product Manager
|
|
217
|
+
> Data: ${now}
|
|
218
|
+
> Versao: v1.0.0
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Metadata
|
|
223
|
+
|
|
224
|
+
| Campo | Valor |
|
|
225
|
+
|-------|-------|
|
|
226
|
+
| ID | EPIC-${epicNum} |
|
|
227
|
+
| Status | backlog |
|
|
228
|
+
| Prioridade | Alta |
|
|
229
|
+
| Owner | Product Manager |
|
|
230
|
+
| Estimativa | ${epic.size === "L" ? "Grande" : "Médio"} |
|
|
231
|
+
| Processo | ${processId} |
|
|
232
|
+
| Criado em | ${now} |
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 1. Objetivo de Negócio (*)
|
|
237
|
+
${epic.objective}
|
|
238
|
+
|
|
239
|
+
## 2. Escopo (*)
|
|
240
|
+
|
|
241
|
+
${scopeList}
|
|
242
|
+
|
|
243
|
+
## 3. Features (*)
|
|
244
|
+
|
|
245
|
+
| ID | Feature | Prioridade | Status |
|
|
246
|
+
|----|---------|------------|--------|
|
|
247
|
+
${featTable}
|
|
248
|
+
|
|
249
|
+
## 4. Dependências
|
|
250
|
+
|
|
251
|
+
${depsText}
|
|
252
|
+
|
|
253
|
+
## 5. Squads
|
|
254
|
+
|
|
255
|
+
${squadsText}
|
|
256
|
+
|
|
257
|
+
## 6. Critérios de Aceite do Épico (*)
|
|
258
|
+
|
|
259
|
+
${criteriaList}
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Changelog
|
|
264
|
+
|
|
265
|
+
| Versão | Data | Autor | Mudança |
|
|
266
|
+
|--------|------|-------|---------|
|
|
267
|
+
| v1.0.0 | ${now} | Creative IA 50 | Gerado via decompose_epic |
|
|
268
|
+
`;
|
|
269
|
+
}
|
|
270
|
+
function generateFeatureMd(feat, stories, epic, processId) {
|
|
271
|
+
const now = new Date().toISOString().split("T")[0];
|
|
272
|
+
const usTable = stories.map((us) => `| ${us.id} | ${us.title} | 5 | Alta | backlog |`).join("\n");
|
|
273
|
+
const criteriaList = feat.criteria.map((c) => `- [ ] ${c}`).join("\n");
|
|
274
|
+
return `# ${feat.id}: ${feat.title}
|
|
275
|
+
|
|
276
|
+
> Processo: ${processId}
|
|
277
|
+
> Autor: Product Manager
|
|
278
|
+
> Data: ${now}
|
|
279
|
+
> Versao: v1.0.0
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Metadata
|
|
284
|
+
|
|
285
|
+
| Campo | Valor |
|
|
286
|
+
|-------|-------|
|
|
287
|
+
| ID | ${feat.id} |
|
|
288
|
+
| Épico | ${epic.id} — ${epic.title} |
|
|
289
|
+
| Status | backlog |
|
|
290
|
+
| Prioridade | ${feat.priority} |
|
|
291
|
+
| Processo | ${processId} |
|
|
292
|
+
| Criado em | ${now} |
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## 1. Descrição (*)
|
|
297
|
+
${feat.description}
|
|
298
|
+
|
|
299
|
+
## 2. User Stories (*)
|
|
300
|
+
|
|
301
|
+
| ID | User Story | Pontos | Prioridade | Status |
|
|
302
|
+
|----|-----------|--------|------------|--------|
|
|
303
|
+
${usTable}
|
|
304
|
+
|
|
305
|
+
## 3. Critérios de Aceite da Feature (*)
|
|
306
|
+
|
|
307
|
+
${criteriaList}
|
|
308
|
+
|
|
309
|
+
## 4. Dependências
|
|
310
|
+
|
|
311
|
+
| Tipo | Descrição | Status |
|
|
312
|
+
|------|-----------|--------|
|
|
313
|
+
| Épico | ${epic.id} — ${epic.title} | backlog |
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Changelog
|
|
318
|
+
|
|
319
|
+
| Versão | Data | Autor | Mudança |
|
|
320
|
+
|--------|------|-------|---------|
|
|
321
|
+
| v1.0.0 | ${now} | Creative IA 50 | Gerado via decompose_epic |
|
|
322
|
+
`;
|
|
323
|
+
}
|
|
324
|
+
function generateUserStoryMd(us, epic, tasks, processId) {
|
|
325
|
+
const now = new Date().toISOString().split("T")[0];
|
|
326
|
+
const taskTable = tasks.map((t) => `| ${t.id} | ${t.title} | ${t.type} | — | ${t.estimate} | backlog |`).join("\n");
|
|
327
|
+
const criteriaText = us.criteria.map((c, i) => {
|
|
328
|
+
if (i === 0)
|
|
329
|
+
return `**Cenário 1: Sucesso**\n- **Dado** ${c}`;
|
|
330
|
+
if (i === 1)
|
|
331
|
+
return `- **Quando** ${c}`;
|
|
332
|
+
if (i === 2)
|
|
333
|
+
return `- **Então** ${c}`;
|
|
334
|
+
return `- **E** ${c}`;
|
|
335
|
+
}).join("\n");
|
|
336
|
+
return `# ${us.id}: ${us.title}
|
|
337
|
+
|
|
338
|
+
> Processo: ${processId}
|
|
339
|
+
> Autor: Tech Lead
|
|
340
|
+
> Data: ${now}
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Metadata
|
|
345
|
+
|
|
346
|
+
| Campo | Valor |
|
|
347
|
+
|-------|-------|
|
|
348
|
+
| ID | ${us.id} |
|
|
349
|
+
| Feature | ${us.featId} |
|
|
350
|
+
| Épico | ${epic.id} — ${epic.title} |
|
|
351
|
+
| Status | backlog |
|
|
352
|
+
| Prioridade | Alta |
|
|
353
|
+
| Story Points | 5 |
|
|
354
|
+
| Processo | ${processId} |
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## 1. User Story (*)
|
|
359
|
+
|
|
360
|
+
**Como** ${us.asA},
|
|
361
|
+
**Quero** ${us.iWant},
|
|
362
|
+
**Para** ${us.soThat}.
|
|
363
|
+
|
|
364
|
+
## 2. Critérios de Aceite (*)
|
|
365
|
+
|
|
366
|
+
${criteriaText}
|
|
367
|
+
|
|
368
|
+
## 3. Tasks (*)
|
|
369
|
+
|
|
370
|
+
| ID | Task | Tipo | Assignee | Estimativa | Status |
|
|
371
|
+
|----|------|------|----------|------------|--------|
|
|
372
|
+
${taskTable}
|
|
373
|
+
|
|
374
|
+
## 4. Definição de Ready (DoR)
|
|
375
|
+
|
|
376
|
+
- [ ] Critérios de aceite escritos e revisados
|
|
377
|
+
- [ ] Dependências mapeadas e resolvidas
|
|
378
|
+
- [ ] Tasks decompostas com estimativa
|
|
379
|
+
|
|
380
|
+
## 5. Definição de Done (DoD)
|
|
381
|
+
|
|
382
|
+
- [ ] Código implementado e revisado (PR aprovado)
|
|
383
|
+
- [ ] Testes unitários passando (cobertura >= 80%)
|
|
384
|
+
- [ ] Critérios de aceite validados
|
|
385
|
+
- [ ] Sem bugs críticos ou bloqueantes
|
|
386
|
+
- [ ] Documentação atualizada
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Changelog
|
|
391
|
+
|
|
392
|
+
| Data | Autor | Mudança |
|
|
393
|
+
|------|-------|---------|
|
|
394
|
+
| ${now} | Creative IA 50 | Gerado via decompose_epic |
|
|
395
|
+
`;
|
|
396
|
+
}
|
|
397
|
+
function generateTaskMd(task, processId) {
|
|
398
|
+
const now = new Date().toISOString().split("T")[0];
|
|
399
|
+
const instructionsList = task.instructions.map((i, idx) => `${idx + 1}. ${i}`).join("\n");
|
|
400
|
+
return `# ${task.id}: ${task.title}
|
|
401
|
+
|
|
402
|
+
> Processo: ${processId}
|
|
403
|
+
> Autor: Tech Lead
|
|
404
|
+
> Data: ${now}
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## Metadata
|
|
409
|
+
|
|
410
|
+
| Campo | Valor |
|
|
411
|
+
|-------|-------|
|
|
412
|
+
| ID | ${task.id} |
|
|
413
|
+
| User Story | ${task.usId} |
|
|
414
|
+
| Feature | ${task.featId} |
|
|
415
|
+
| Tipo | ${task.type} |
|
|
416
|
+
| Status | backlog |
|
|
417
|
+
| Estimativa | ${task.estimate} |
|
|
418
|
+
| Processo | ${processId} |
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## 1. Objetivo (*)
|
|
423
|
+
|
|
424
|
+
${task.title}.
|
|
425
|
+
|
|
426
|
+
## 2. Instruções de Implementação (*)
|
|
427
|
+
|
|
428
|
+
${instructionsList}
|
|
429
|
+
|
|
430
|
+
## 3. Critérios de Aceite (*)
|
|
431
|
+
|
|
432
|
+
- [ ] Funcionalidade implementada conforme especificação
|
|
433
|
+
- [ ] Tratamento de erros implementado
|
|
434
|
+
- [ ] Testes passando (cobertura >= 80%)
|
|
435
|
+
- [ ] Sem warnings de lint/build
|
|
436
|
+
|
|
437
|
+
## 4. Definition of Done (*)
|
|
438
|
+
|
|
439
|
+
- [ ] Código implementado conforme instruções
|
|
440
|
+
- [ ] Code review aprovado
|
|
441
|
+
- [ ] Testes escritos e passando
|
|
442
|
+
- [ ] Documentação atualizada
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## Changelog
|
|
447
|
+
|
|
448
|
+
| Data | Autor | Mudança |
|
|
449
|
+
|------|-------|---------|
|
|
450
|
+
| ${now} | Creative IA 50 | Gerado via decompose_epic |
|
|
451
|
+
`;
|
|
452
|
+
}
|
|
453
|
+
// ============================================================
|
|
454
|
+
// MAIN FUNCTION — decompose_epic
|
|
455
|
+
// ============================================================
|
|
456
|
+
export async function decomposeEpic(params) {
|
|
457
|
+
const { processId, epicId, processDir } = params;
|
|
458
|
+
// 1. Find and read the epicos markdown
|
|
459
|
+
const possibleFiles = [
|
|
460
|
+
`03-epicos-${processId}.md`,
|
|
461
|
+
`03-epicos.md`,
|
|
462
|
+
];
|
|
463
|
+
// Also search for any file starting with 03-epicos
|
|
464
|
+
let epicsMdPath = "";
|
|
465
|
+
let epicsMdContent = "";
|
|
466
|
+
for (const f of possibleFiles) {
|
|
467
|
+
const p = join(processDir, f);
|
|
468
|
+
if (existsSync(p)) {
|
|
469
|
+
epicsMdPath = p;
|
|
470
|
+
epicsMdContent = await readFile(p, "utf-8");
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
// Fallback: search for any 03-epicos*.md file
|
|
475
|
+
if (!epicsMdContent) {
|
|
476
|
+
const { readdirSync } = await import("node:fs");
|
|
477
|
+
try {
|
|
478
|
+
const files = readdirSync(processDir);
|
|
479
|
+
const epicFile = files.find((f) => f.startsWith("03-epicos") && f.endsWith(".md"));
|
|
480
|
+
if (epicFile) {
|
|
481
|
+
epicsMdPath = join(processDir, epicFile);
|
|
482
|
+
epicsMdContent = await readFile(epicsMdPath, "utf-8");
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
catch { /* ignore */ }
|
|
486
|
+
}
|
|
487
|
+
if (!epicsMdContent) {
|
|
488
|
+
return `Erro: Não encontrei o markdown de épicos no processo ${processId}. Esperado: 03-epicos*.md em ${processDir}`;
|
|
489
|
+
}
|
|
490
|
+
// 2. Parse the requested epic
|
|
491
|
+
const epic = parseEpicFromMarkdown(epicsMdContent, epicId);
|
|
492
|
+
if (!epic) {
|
|
493
|
+
return `Erro: Épico ${epicId} não encontrado no markdown. Épicos disponíveis: ${(epicsMdContent.match(/## EP-\d{2}/g) || []).join(", ")}`;
|
|
494
|
+
}
|
|
495
|
+
// 3. Check existing artifacts to determine starting indices
|
|
496
|
+
const { readdirSync } = await import("node:fs");
|
|
497
|
+
let maxFeat = 0, maxUs = 0, maxTask = 0;
|
|
498
|
+
try {
|
|
499
|
+
const featDir = join(processDir, "features");
|
|
500
|
+
if (existsSync(featDir)) {
|
|
501
|
+
const feats = readdirSync(featDir).filter((f) => f.match(/^FEAT-\d+\.md$/));
|
|
502
|
+
feats.forEach((f) => {
|
|
503
|
+
const num = parseInt(f.replace("FEAT-", "").replace(".md", ""), 10);
|
|
504
|
+
if (num > maxFeat)
|
|
505
|
+
maxFeat = num;
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
catch { /* ignore */ }
|
|
510
|
+
try {
|
|
511
|
+
const usDir = join(processDir, "user-stories");
|
|
512
|
+
if (existsSync(usDir)) {
|
|
513
|
+
const uss = readdirSync(usDir).filter((f) => f.match(/^US-\d+\.md$/));
|
|
514
|
+
uss.forEach((f) => {
|
|
515
|
+
const num = parseInt(f.replace("US-", "").replace(".md", ""), 10);
|
|
516
|
+
if (num > maxUs)
|
|
517
|
+
maxUs = num;
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
catch { /* ignore */ }
|
|
522
|
+
try {
|
|
523
|
+
const taskDir = join(processDir, "tasks");
|
|
524
|
+
if (existsSync(taskDir)) {
|
|
525
|
+
const tasks = readdirSync(taskDir).filter((f) => f.match(/^TASK-\d+\.md$/));
|
|
526
|
+
tasks.forEach((f) => {
|
|
527
|
+
const num = parseInt(f.replace("TASK-", "").replace(".md", ""), 10);
|
|
528
|
+
if (num > maxTask)
|
|
529
|
+
maxTask = num;
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
catch { /* ignore */ }
|
|
534
|
+
// 4. Decompose
|
|
535
|
+
const features = decomposeToFeatures(epic, maxFeat + 1);
|
|
536
|
+
let usIdx = maxUs + 1;
|
|
537
|
+
let taskIdx = maxTask + 1;
|
|
538
|
+
const allStories = [];
|
|
539
|
+
const allTasks = [];
|
|
540
|
+
for (const feat of features) {
|
|
541
|
+
const stories = decomposeToUserStories(feat, epic, usIdx);
|
|
542
|
+
usIdx += stories.length;
|
|
543
|
+
feat.userStories = stories.map((s) => s.id);
|
|
544
|
+
allStories.push({ feature: feat, stories });
|
|
545
|
+
for (const story of stories) {
|
|
546
|
+
const tasks = decomposeToTasks(story, taskIdx);
|
|
547
|
+
taskIdx += tasks.length;
|
|
548
|
+
story.tasks = tasks.map((t) => t.id);
|
|
549
|
+
allTasks.push({ story, tasks });
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
// 5. Create directories
|
|
553
|
+
const dirs = ["epics", "features", "user-stories", "tasks"];
|
|
554
|
+
for (const d of dirs) {
|
|
555
|
+
await mkdir(join(processDir, d), { recursive: true });
|
|
556
|
+
}
|
|
557
|
+
// 6. Write files
|
|
558
|
+
const epicNum = epic.id.replace("EP-", "").padStart(3, "0");
|
|
559
|
+
const filesCreated = [];
|
|
560
|
+
// Epic
|
|
561
|
+
const epicPath = join(processDir, "epics", `EPIC-${epicNum}.md`);
|
|
562
|
+
await writeFile(epicPath, generateEpicMd(epic, features, processId), "utf-8");
|
|
563
|
+
filesCreated.push(`epics/EPIC-${epicNum}.md`);
|
|
564
|
+
// Features
|
|
565
|
+
for (const { feature, stories } of allStories) {
|
|
566
|
+
const featPath = join(processDir, "features", `${feature.id}.md`);
|
|
567
|
+
await writeFile(featPath, generateFeatureMd(feature, stories, epic, processId), "utf-8");
|
|
568
|
+
filesCreated.push(`features/${feature.id}.md`);
|
|
569
|
+
}
|
|
570
|
+
// User Stories
|
|
571
|
+
for (const { story, tasks } of allTasks) {
|
|
572
|
+
const usPath = join(processDir, "user-stories", `${story.id}.md`);
|
|
573
|
+
await writeFile(usPath, generateUserStoryMd(story, epic, tasks, processId), "utf-8");
|
|
574
|
+
filesCreated.push(`user-stories/${story.id}.md`);
|
|
575
|
+
}
|
|
576
|
+
// Tasks
|
|
577
|
+
for (const { tasks } of allTasks) {
|
|
578
|
+
for (const task of tasks) {
|
|
579
|
+
const taskPath = join(processDir, "tasks", `${task.id}.md`);
|
|
580
|
+
await writeFile(taskPath, generateTaskMd(task, processId), "utf-8");
|
|
581
|
+
filesCreated.push(`tasks/${task.id}.md`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
// 7. Summary
|
|
585
|
+
const totalFeats = features.length;
|
|
586
|
+
const totalUs = allStories.reduce((sum, s) => sum + s.stories.length, 0);
|
|
587
|
+
const totalTasks = allTasks.reduce((sum, t) => sum + t.tasks.length, 0);
|
|
588
|
+
const summary = [
|
|
589
|
+
`Decomposição do ${epic.id} — ${epic.title} concluída.`,
|
|
590
|
+
``,
|
|
591
|
+
`Artefatos gerados:`,
|
|
592
|
+
` • 1 épico detalhado (EPIC-${epicNum})`,
|
|
593
|
+
` • ${totalFeats} features (${features.map((f) => f.id).join(", ")})`,
|
|
594
|
+
` • ${totalUs} user stories`,
|
|
595
|
+
` • ${totalTasks} tasks`,
|
|
596
|
+
``,
|
|
597
|
+
`Arquivos criados (${filesCreated.length}):`,
|
|
598
|
+
...filesCreated.map((f) => ` ${f}`),
|
|
599
|
+
``,
|
|
600
|
+
`Diretório: ${processDir}`,
|
|
601
|
+
].join("\n");
|
|
602
|
+
return summary;
|
|
603
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Geradores de Diagramas — CSS puro usando classes bd-* do _base-styles.css
|
|
3
|
+
* Gera diagramas visuais de workflow, arquitetura, dominios, riscos, integracoes e benchmark.
|
|
4
|
+
* IP protegido: os templates de diagrama nunca sao expostos ao cliente.
|
|
5
|
+
*/
|
|
6
|
+
interface FaseWorkflow {
|
|
7
|
+
name: string;
|
|
8
|
+
status: "concluido" | "ativo" | "pendente";
|
|
9
|
+
subtitle?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function renderWorkflowDiagram(phases: FaseWorkflow[]): string;
|
|
12
|
+
export declare function renderArchitectureDiagram(): string;
|
|
13
|
+
interface NoDominio {
|
|
14
|
+
name: string;
|
|
15
|
+
type: "core" | "supporting" | "generic";
|
|
16
|
+
squad: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function renderDomainMap(domains: NoDominio[]): string;
|
|
19
|
+
interface ItemRisco {
|
|
20
|
+
name: string;
|
|
21
|
+
probability: "baixa" | "media" | "alta";
|
|
22
|
+
impact: "baixo" | "medio" | "alto";
|
|
23
|
+
category?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function renderRiskMatrix(risks: ItemRisco[]): string;
|
|
26
|
+
interface NoIntegracao {
|
|
27
|
+
name: string;
|
|
28
|
+
type: "interno" | "externo" | "gateway";
|
|
29
|
+
protocol?: string;
|
|
30
|
+
}
|
|
31
|
+
interface LinkIntegracao {
|
|
32
|
+
from: string;
|
|
33
|
+
to: string;
|
|
34
|
+
label?: string;
|
|
35
|
+
protocol?: string;
|
|
36
|
+
}
|
|
37
|
+
export declare function renderIntegrationFlow(nodes: NoIntegracao[], links: LinkIntegracao[]): string;
|
|
38
|
+
interface Concorrente {
|
|
39
|
+
nome: string;
|
|
40
|
+
tipo: "banco" | "fintech" | "financeira" | "cooperativa" | "nosso";
|
|
41
|
+
indicadores: Record<string, number | string>;
|
|
42
|
+
destaque?: boolean;
|
|
43
|
+
}
|
|
44
|
+
interface DimensaoBenchmark {
|
|
45
|
+
nome: string;
|
|
46
|
+
chave: string;
|
|
47
|
+
unidade?: string;
|
|
48
|
+
maiorMelhor: boolean;
|
|
49
|
+
}
|
|
50
|
+
export declare function renderBenchmarkMercado(concorrentes: Concorrente[], dimensoes: DimensaoBenchmark[], titulo?: string, fontes?: string[]): string;
|
|
51
|
+
export {};
|