@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.
Files changed (64) hide show
  1. package/README.md +41 -0
  2. package/dist/config/cloud-proxy.d.ts +15 -0
  3. package/dist/config/cloud-proxy.js +63 -0
  4. package/dist/config/cloudwatch-store.d.ts +13 -0
  5. package/dist/config/cloudwatch-store.js +66 -0
  6. package/dist/config/license.d.ts +29 -0
  7. package/dist/config/license.js +165 -0
  8. package/dist/config/ssm-store.d.ts +2 -0
  9. package/dist/config/ssm-store.js +38 -0
  10. package/dist/config/telemetry.d.ts +17 -0
  11. package/dist/config/telemetry.js +93 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.js +460 -0
  14. package/dist/knowledge/dynamo-store.d.ts +17 -0
  15. package/dist/knowledge/dynamo-store.js +85 -0
  16. package/dist/knowledge/embeddings.d.ts +2 -0
  17. package/dist/knowledge/embeddings.js +36 -0
  18. package/dist/knowledge/loader.d.ts +8 -0
  19. package/dist/knowledge/loader.js +57 -0
  20. package/dist/lambda-package.zip +0 -0
  21. package/dist/lambda.d.ts +22 -0
  22. package/dist/lambda.js +496 -0
  23. package/dist/package.json +1 -0
  24. package/dist/tools/advance-process.d.ts +7 -0
  25. package/dist/tools/advance-process.js +128 -0
  26. package/dist/tools/analyze-code.d.ts +7 -0
  27. package/dist/tools/analyze-code.js +131 -0
  28. package/dist/tools/analyze-docs.d.ts +8 -0
  29. package/dist/tools/analyze-docs.js +147 -0
  30. package/dist/tools/config-registry.d.ts +3 -0
  31. package/dist/tools/config-registry.js +20 -0
  32. package/dist/tools/create-process.d.ts +6 -0
  33. package/dist/tools/create-process.js +257 -0
  34. package/dist/tools/decompose-epic.d.ts +7 -0
  35. package/dist/tools/decompose-epic.js +603 -0
  36. package/dist/tools/diagrams.d.ts +51 -0
  37. package/dist/tools/diagrams.js +304 -0
  38. package/dist/tools/generate-report.d.ts +9 -0
  39. package/dist/tools/generate-report.js +891 -0
  40. package/dist/tools/generate-wiki.d.ts +10 -0
  41. package/dist/tools/generate-wiki.js +700 -0
  42. package/dist/tools/get-architecture.d.ts +6 -0
  43. package/dist/tools/get-architecture.js +78 -0
  44. package/dist/tools/get-code-standards.d.ts +7 -0
  45. package/dist/tools/get-code-standards.js +52 -0
  46. package/dist/tools/init-process.d.ts +7 -0
  47. package/dist/tools/init-process.js +82 -0
  48. package/dist/tools/knowledge-crud.d.ts +26 -0
  49. package/dist/tools/knowledge-crud.js +142 -0
  50. package/dist/tools/logo-base64.d.ts +1 -0
  51. package/dist/tools/logo-base64.js +1 -0
  52. package/dist/tools/logs-query.d.ts +15 -0
  53. package/dist/tools/logs-query.js +46 -0
  54. package/dist/tools/reverse-engineer.d.ts +13 -0
  55. package/dist/tools/reverse-engineer.js +956 -0
  56. package/dist/tools/semantic-search.d.ts +7 -0
  57. package/dist/tools/semantic-search.js +68 -0
  58. package/dist/tools/update-process.d.ts +17 -0
  59. package/dist/tools/update-process.js +195 -0
  60. package/dist/tools/validate-idea.d.ts +7 -0
  61. package/dist/tools/validate-idea.js +339 -0
  62. package/dist/tools/validate-process.d.ts +6 -0
  63. package/dist/tools/validate-process.js +102 -0
  64. package/package.json +31 -0
@@ -0,0 +1,891 @@
1
+ /**
2
+ * Tool: generate_report
3
+ * Gera relatório HTML com toggle light/dark theme, navegação estruturada,
4
+ * suporte a Mermaid diagrams e layout responsivo.
5
+ * O template e CSS são IP protegido — nunca expostos como source.
6
+ */
7
+ import { writeFile, mkdir } from "node:fs/promises";
8
+ import { join } from "node:path";
9
+ import { homedir } from "node:os";
10
+ import { loadTemplate } from "../knowledge/loader.js";
11
+ import { renderWorkflowDiagram, renderArchitectureDiagram, renderDomainMap, renderRiskMatrix, renderIntegrationFlow, renderBenchmarkMercado, } from "./diagrams.js";
12
+ import { LOGO_BASE64 } from "./logo-base64.js";
13
+ const SECTION_ICONS = {
14
+ "Dominios": "domain", "Squads": "groups", "Arquitetura": "architecture",
15
+ "Integracoes": "cable", "Processos": "history", "Decisoes": "gavel",
16
+ "Qualidade": "verified", "Riscos": "warning", "Recomendacao": "rocket_launch",
17
+ "Ideia": "lightbulb", "Analysis": "analytics", "Passed": "check_circle",
18
+ "Issues": "error", "Result": "task_alt", "Layer": "layers",
19
+ "Folder": "folder_open", "Error": "bug_report", "API": "api",
20
+ "Dependency": "sync_alt", "Architecture": "architecture",
21
+ "L1": "code", "L2": "science", "L3": "account_tree", "L4": "shield",
22
+ "New Process": "add_circle", "Required": "checklist", "Workflow": "route",
23
+ "Golden": "star", "Benchmark": "leaderboard", "Estimativa": "calculate",
24
+ "Infra": "cloud", "Enviar": "send", "Receber": "call_received",
25
+ "Chave": "key", "QR": "qr_code_2", "Agendad": "schedule_send",
26
+ "Extrato": "receipt_long", "Observ": "monitoring", "Monitoramento": "monitoring",
27
+ "Onda": "waves", "Resumo": "table_chart", "Sequencia": "route",
28
+ "Diagrama": "schema", "Epico": "inventory_2",
29
+ "default": "article",
30
+ };
31
+ function getIcon(title) {
32
+ for (const [key, icon] of Object.entries(SECTION_ICONS)) {
33
+ if (title.includes(key))
34
+ return icon;
35
+ }
36
+ return SECTION_ICONS.default;
37
+ }
38
+ function parseSections(content) {
39
+ const sections = [];
40
+ let current = null;
41
+ for (const line of content.split("\n")) {
42
+ if (line.startsWith("## ")) {
43
+ if (current)
44
+ sections.push(current);
45
+ current = { title: line.replace("## ", "").replace(/^\d+\.\s*/, ""), lines: [] };
46
+ }
47
+ else if (!line.startsWith("# ") && current) {
48
+ current.lines.push(line);
49
+ }
50
+ }
51
+ if (current)
52
+ sections.push(current);
53
+ return sections;
54
+ }
55
+ function fmt(text) {
56
+ return text
57
+ .replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
58
+ .replace(/`([^`]+)`/g, "<code>$1</code>");
59
+ }
60
+ function renderSection(s, idx, allSections) {
61
+ const icon = getIcon(s.title);
62
+ const id = `s${idx}`;
63
+ const body = s.lines
64
+ .filter((l) => l.trim())
65
+ .map((l) => {
66
+ if (l.startsWith("- **"))
67
+ return `<li>${fmt(l.slice(2))}</li>`;
68
+ if (l.startsWith("- "))
69
+ return `<li>${fmt(l.slice(2))}</li>`;
70
+ if (l.startsWith("> "))
71
+ return `<p class="lead" style="font-style:italic;border-left:3px solid var(--accent);padding-left:14px;">${l.slice(2)}</p>`;
72
+ if (l.startsWith("```"))
73
+ return "";
74
+ if (l.startsWith("### "))
75
+ return `<h3>${l.replace(/^###\s*/, "")}</h3>`;
76
+ return `<p>${fmt(l)}</p>`;
77
+ })
78
+ .join("\n");
79
+ const hasList = body.includes("<li>");
80
+ // Auto-inject diagrams based on section title
81
+ let diagram = "";
82
+ const titleLower = s.title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
83
+ if (titleLower.includes("dominios") || titleLower.includes("domains")) {
84
+ const domains = s.lines
85
+ .filter((l) => l.startsWith("- **"))
86
+ .map((l) => {
87
+ const match = l.match(/\*\*([^*]+)\*\*\s*\((\w+)\)\s*[—-]\s*(.+)/);
88
+ if (match)
89
+ return { name: match[1], type: match[2], squad: match[3].trim() };
90
+ return null;
91
+ })
92
+ .filter(Boolean);
93
+ if (domains.length > 0)
94
+ diagram = renderDomainMap(domains);
95
+ }
96
+ if (titleLower.includes("arquitetura") || titleLower.includes("architecture")) {
97
+ diagram = renderArchitectureDiagram();
98
+ }
99
+ if (titleLower.includes("riscos") || titleLower.includes("risks")) {
100
+ const risks = s.lines
101
+ .filter((l) => l.startsWith("- **"))
102
+ .map((l) => {
103
+ const match = l.match(/\*\*([^*]+)\*\*/);
104
+ const name = match ? match[1] : l.slice(4, 40);
105
+ const lower = l.toLowerCase();
106
+ const isRegulatorio = lower.includes("regulat");
107
+ const isSeguranca = lower.includes("seguranca") || lower.includes("lgpd");
108
+ return {
109
+ name,
110
+ probability: (isRegulatorio ? "alta" : "media"),
111
+ impact: (isSeguranca || isRegulatorio ? "alto" : "medio"),
112
+ category: isRegulatorio ? "Regulatorio" : isSeguranca ? "Seguranca" : "Tecnico",
113
+ };
114
+ });
115
+ if (risks.length > 0)
116
+ diagram = renderRiskMatrix(risks);
117
+ }
118
+ if (titleLower.includes("integracoes") || titleLower.includes("integrations")) {
119
+ const nodes = [];
120
+ const links = [];
121
+ nodes.push({ name: "BFF", type: "gateway", protocol: "GraphQL" });
122
+ for (const l of s.lines) {
123
+ if (l.startsWith("- **")) {
124
+ const match = l.match(/\*\*([^*]+)\*\*\s*\(([^)]+)\)/);
125
+ if (match) {
126
+ const isExternal = match[2].includes("nova") || match[2].includes("external");
127
+ nodes.push({ name: match[1], type: isExternal ? "externo" : "interno" });
128
+ links.push({ from: "BFF", to: match[1], protocol: match[2] });
129
+ }
130
+ }
131
+ }
132
+ if (nodes.length > 1)
133
+ diagram = renderIntegrationFlow(nodes, links);
134
+ }
135
+ if (titleLower.includes("recomendacao") || titleLower.includes("workflow") || titleLower.includes("proximo")) {
136
+ diagram = renderWorkflowDiagram([
137
+ { name: "Stakeholder", status: "concluido", subtitle: "01" },
138
+ { name: "Intake", status: "ativo", subtitle: "02" },
139
+ { name: "Epicos", status: "pendente", subtitle: "03" },
140
+ { name: "Estrategia", status: "pendente", subtitle: "04/05" },
141
+ { name: "Execucao", status: "pendente", subtitle: "06+" },
142
+ ]);
143
+ }
144
+ if (titleLower.includes("benchmark")) {
145
+ const concorrentes = [];
146
+ const dimensoes = [];
147
+ const tableLines = s.lines.filter((l) => l.includes("|") && !l.match(/^\s*\|?\s*[-:]+/));
148
+ if (tableLines.length >= 2) {
149
+ const headers = tableLines[0].split("|").map((h) => h.trim()).filter(Boolean);
150
+ const nomes = headers.slice(1);
151
+ const tipoLine = s.lines.find((l) => l.toLowerCase().includes("tipo:") || l.toLowerCase().includes("| tipo"));
152
+ const tipos = [];
153
+ if (tipoLine) {
154
+ const parts = tipoLine.split("|").map((p) => p.trim()).filter(Boolean);
155
+ tipos.push(...parts.slice(1));
156
+ }
157
+ for (let ni = 0; ni < nomes.length; ni++) {
158
+ const tipoStr = (tipos[ni] || "banco").toLowerCase();
159
+ const tipo = (tipoStr.includes("nosso") ? "nosso" : tipoStr.includes("fintech") ? "fintech" : tipoStr.includes("financeira") ? "financeira" : tipoStr.includes("cooperativa") ? "cooperativa" : "banco");
160
+ concorrentes.push({ nome: nomes[ni], tipo, indicadores: {} });
161
+ }
162
+ for (const line of tableLines.slice(1)) {
163
+ if (line.toLowerCase().includes("tipo"))
164
+ continue;
165
+ const cells = line.split("|").map((c) => c.trim()).filter(Boolean);
166
+ if (cells.length < 2)
167
+ continue;
168
+ const dimNome = cells[0];
169
+ const chave = dimNome.toLowerCase().replace(/\s+/g, "_").normalize("NFD").replace(/[\u0300-\u036f]/g, "");
170
+ const maiorMelhor = !dimNome.toLowerCase().includes("taxa") && !dimNome.toLowerCase().includes("prazo") && !dimNome.toLowerCase().includes("tempo");
171
+ dimensoes.push({ nome: dimNome, chave, maiorMelhor });
172
+ for (let ci = 1; ci < cells.length && ci - 1 < concorrentes.length; ci++) {
173
+ const val = cells[ci];
174
+ const numVal = parseFloat(val.replace(/[^\d.,]/g, "").replace(",", "."));
175
+ concorrentes[ci - 1].indicadores[chave] = isNaN(numVal) ? val : numVal;
176
+ }
177
+ }
178
+ }
179
+ if (concorrentes.length > 0 && dimensoes.length > 0) {
180
+ const fontes = [];
181
+ let inFontes = false;
182
+ for (const l of s.lines) {
183
+ if (l.includes("**Fontes:**") || l.includes("Fontes:")) {
184
+ inFontes = true;
185
+ continue;
186
+ }
187
+ if (inFontes && l.startsWith("- ")) {
188
+ fontes.push(l.slice(2).replace(/\*\*/g, ""));
189
+ }
190
+ else if (inFontes && !l.trim()) {
191
+ inFontes = false;
192
+ }
193
+ }
194
+ diagram = renderBenchmarkMercado(concorrentes, dimensoes, undefined, fontes.length > 0 ? fontes : undefined);
195
+ }
196
+ }
197
+ return `<section class="card" id="${id}">
198
+ <h2><span class="material-symbols-outlined">${icon}</span> ${s.title}</h2>
199
+ ${hasList ? `<ul>${body}</ul>` : body}
200
+ ${diagram}
201
+ </section>`;
202
+ }
203
+ function renderToc(sections) {
204
+ return sections.map((s, i) => `<a href="#s${i}"><span class="material-symbols-outlined" style="font-size:.9rem;vertical-align:middle;margin-right:4px;">${getIcon(s.title)}</span>${s.title}</a>`).join("\n");
205
+ }
206
+ // ============================================================
207
+ // CSS DUAL THEME — Dark (default) + Light toggle
208
+ // ============================================================
209
+ function getDualThemeCSS() {
210
+ return `
211
+ /* === DARK THEME (default) === */
212
+ :root {
213
+ --bg: #0A0F1A; --paper: #111827; --ink: #E5E7EB; --muted: #9AA3AD;
214
+ --brand: #2F6DB3; --brand-2: #4FB3FF; --accent: #00E0FF; --line: #1E293B;
215
+ --soft: #0F1C2E; --shadow: 0 14px 40px rgba(0,224,255,0.06);
216
+ --silver: #C9CCD1; --warn: #F59E0B; --warn-bg: #1C1708;
217
+ --danger: #EF4444; --danger-bg: #1C0808; --ok: #22C55E; --ok-bg: #081C10;
218
+ --cover-from: #0F1C2E; --cover-mid: #1B3B6F; --cover-to: #2F6DB3;
219
+ --toc-bg: rgba(17,24,39,.92); --mini-from: #111827; --mini-to: #0F1C2E;
220
+ --body-bg: radial-gradient(circle at top right,rgba(0,224,255,.06),transparent 40%),radial-gradient(circle at bottom left,rgba(47,109,179,.08),transparent 50%),var(--bg);
221
+ }
222
+ /* === LIGHT THEME === */
223
+ [data-theme="light"] {
224
+ --bg: #F8FAFC; --paper: #FFFFFF; --ink: #1E293B; --muted: #475569;
225
+ --brand: #1D4ED8; --brand-2: #2563EB; --accent: #0369A1; --line: #CBD5E1;
226
+ --soft: #F1F5F9; --shadow: 0 4px 20px rgba(0,0,0,0.08);
227
+ --silver: #334155; --warn: #B45309; --warn-bg: #FFFBEB;
228
+ --danger: #DC2626; --danger-bg: #FEF2F2; --ok: #15803D; --ok-bg: #F0FDF4;
229
+ --cover-from: #1E40AF; --cover-mid: #2563EB; --cover-to: #1D4ED8;
230
+ --toc-bg: rgba(255,255,255,.97); --mini-from: #FFFFFF; --mini-to: #F8FAFC;
231
+ --body-bg: linear-gradient(180deg,#F8FAFC,#EFF6FF);
232
+ }
233
+ * { box-sizing: border-box; }
234
+ html { scroll-behavior: smooth; }
235
+ body { margin:0; font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif; letter-spacing:.01em; background:var(--body-bg); color:var(--ink); line-height:1.7; transition:background .3s,color .3s; }
236
+ .page { width:92%; max-width:1600px; margin:0 auto; padding:clamp(16px,3vw,40px) clamp(16px,4vw,48px) 80px; }
237
+ .cover { background:linear-gradient(135deg,var(--cover-from) 0%,var(--cover-mid) 50%,var(--cover-to) 100%); color:#fff; border-radius:clamp(16px,2vw,28px); padding:clamp(24px,3vw,40px); box-shadow:0 14px 40px rgba(0,224,255,.08); position:relative; overflow:hidden; border:1px solid rgba(0,224,255,.1); }
238
+ .cover::after { content:""; position:absolute; width:clamp(160px,22vw,300px); height:clamp(160px,22vw,300px); right:-70px; top:-80px; border-radius:50%; background:radial-gradient(circle,rgba(0,224,255,.12),transparent 70%); }
239
+ .brand { display:flex; align-items:center; gap:clamp(12px,2vw,22px); position:relative; z-index:1; }
240
+ .brand-logo { width:64px; height:64px; border-radius:14px; background:rgba(255,255,255,0.08); padding:4px; flex-shrink:0; }
241
+ [data-theme="light"] .brand-logo { background:rgba(0,0,0,0.06); }
242
+ .eyebrow { letter-spacing:.14em; text-transform:uppercase; font-size:clamp(.68rem,1vw,.82rem); opacity:.88; margin-bottom:8px; }
243
+ h1 { margin:0 0 10px; font-size:clamp(1.4rem,3.5vw,2.4rem); line-height:1.08; color:#fff; }
244
+ .subtitle { max-width:860px; font-size:clamp(.92rem,1.4vw,1.08rem); color:#fff; margin:0; }
245
+ .cover-meta { margin-top:22px; display:flex; flex-wrap:wrap; gap:8px; position:relative; z-index:1; }
246
+ .pill { border:1px solid rgba(0,224,255,.2); background:rgba(0,224,255,.08); color:var(--silver); padding:5px 12px; border-radius:999px; font-size:clamp(.78rem,1vw,.9rem); letter-spacing:.04em; text-transform:uppercase; }
247
+ .cover .pill { color:#C9CCD1; }
248
+ [data-theme="light"] .pill { border-color:rgba(29,78,216,.2); background:rgba(29,78,216,.08); }
249
+ [data-theme="light"] .cover .pill { color:rgba(255,255,255,.85); border-color:rgba(255,255,255,.25); background:rgba(255,255,255,.12); }
250
+ .layout { display:grid; grid-template-columns:clamp(220px,22%,300px) minmax(0,1fr); gap:clamp(14px,2vw,26px); margin-top:clamp(16px,2vw,28px); }
251
+ .toc { position:sticky; top:18px; align-self:start; background:var(--toc-bg); backdrop-filter:blur(14px); border:1px solid var(--line); border-radius:22px; box-shadow:var(--shadow); padding:clamp(14px,2vw,24px); transition:background .3s; }
252
+ .toc h2 { margin:0 0 14px; font-size:clamp(.88rem,1.1vw,1rem); color:var(--brand); }
253
+ .toc a { display:block; color:var(--silver); text-decoration:none; padding:7px 10px; border-radius:10px; font-size:clamp(.82rem,1vw,.95rem); margin-bottom:3px; transition:background .2s,color .2s; }
254
+ .toc a:hover { background:rgba(0,224,255,.06); color:var(--accent); }
255
+ .toc .toc-section { font-size:.72rem; text-transform:uppercase; letter-spacing:.1em; color:var(--muted); margin:12px 0 4px 10px; }
256
+ .content { display:grid; gap:clamp(12px,1.5vw,20px); }
257
+ section.card { background:var(--paper); border:1px solid var(--line); border-radius:clamp(16px,2vw,24px); box-shadow:var(--shadow); padding:clamp(18px,2.5vw,32px); transition:background .3s,border-color .3s; }
258
+ section.card h2 { margin:0 0 14px; font-size:clamp(1.15rem,2vw,1.5rem); color:var(--brand); }
259
+ h3 { margin:20px 0 10px; color:var(--brand-2); font-size:clamp(.95rem,1.3vw,1.08rem); }
260
+ h4 { margin:16px 0 8px; color:var(--ink); font-size:clamp(.88rem,1.1vw,1rem); }
261
+ p { margin:10px 0 14px; color:var(--muted); }
262
+ strong { color:var(--ink); }
263
+ .lead { font-size:clamp(.98rem,1.4vw,1.08rem); }
264
+ .grid { display:grid; gap:16px; }
265
+ .grid.two { grid-template-columns:repeat(auto-fit,minmax(260px,1fr)); }
266
+ .grid.three { grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); }
267
+ .mini-card { border:1px solid var(--line); border-radius:18px; background:linear-gradient(180deg,var(--mini-from),var(--mini-to)); padding:18px; transition:background .3s; }
268
+ .mini-card h4 { margin:0 0 8px; font-size:.98rem; color:var(--brand); }
269
+ table { width:100%; border-collapse:collapse; margin-top:12px; border:1px solid var(--line); border-radius:16px; overflow:hidden; background:var(--paper); transition:background .3s; }
270
+ th,td { padding:11px 14px; text-align:left; vertical-align:top; border-bottom:1px solid var(--line); }
271
+ th { background:var(--soft); color:var(--brand-2); font-weight:700; font-size:.93rem; }
272
+ tr:last-child td { border-bottom:none; }
273
+ .badge { display:inline-block; padding:3px 10px; border-radius:999px; font-size:.8rem; font-weight:600; letter-spacing:.04em; }
274
+ .badge.accepted { background:var(--ok-bg); color:var(--ok); }
275
+ .badge.proposed { background:rgba(47,109,179,.2); color:#4FB3FF; }
276
+ .badge.warn { background:var(--warn-bg); color:var(--warn); }
277
+ .badge.danger { background:var(--danger-bg); color:var(--danger); }
278
+ .dep-tag { display:inline-block; background:rgba(0,224,255,.08); border:1px solid rgba(0,224,255,.15); padding:2px 8px; border-radius:6px; font-size:.75rem; margin-right:4px; color:var(--accent); font-weight:600; }
279
+ [data-theme="light"] .dep-tag { background:rgba(3,105,161,.08); border-color:rgba(3,105,161,.2); color:#0369A1; }
280
+ .squad-tag { display:inline-block; background:rgba(47,109,179,.15); border:1px solid rgba(47,109,179,.25); padding:2px 8px; border-radius:6px; font-size:.75rem; margin-right:4px; color:var(--brand-2); }
281
+ [data-theme="light"] .squad-tag { background:rgba(29,78,216,.08); border-color:rgba(29,78,216,.2); color:#1D4ED8; }
282
+ .size-badge { display:inline-block; padding:3px 10px; border-radius:8px; font-size:.75rem; font-weight:700; color:#fff; }
283
+ .size-L { background:#EF4444; }
284
+ .size-M { background:#2F6DB3; }
285
+ .callout { border:1px solid rgba(0,224,255,.15); background:linear-gradient(180deg,var(--mini-from),var(--mini-to)); border-radius:18px; padding:16px 18px; color:var(--ink); margin:14px 0; transition:background .3s; }
286
+ .scope-list { list-style:none; padding:0; margin:8px 0; }
287
+ .scope-list li { padding:5px 0 5px 20px; position:relative; font-size:.9rem; color:var(--muted); }
288
+ .scope-list li::before { content:"▸"; position:absolute; left:0; color:var(--accent); font-weight:bold; }
289
+ .criteria-list { list-style:none; padding:0; margin:8px 0; }
290
+ .criteria-list li { padding:5px 0 5px 24px; position:relative; font-size:.9rem; color:var(--muted); }
291
+ .criteria-list li::before { content:"☐"; position:absolute; left:0; color:var(--brand-2); }
292
+ .epic-card { background:linear-gradient(180deg,var(--mini-from),var(--mini-to)); border:1px solid var(--line); border-radius:18px; padding:clamp(16px,2vw,24px); margin-bottom:16px; transition:background .3s,border-color .3s; }
293
+ .epic-card:hover { border-color:rgba(0,224,255,.2); }
294
+ .epic-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:12px; flex-wrap:wrap; gap:8px; }
295
+ .epic-header h3 { margin:0; font-size:clamp(1rem,1.5vw,1.15rem); color:var(--ink); }
296
+ .wave { border-left:4px solid var(--brand); padding:16px 20px; background:linear-gradient(180deg,var(--mini-from),var(--mini-to)); border-radius:0 18px 18px 0; margin-bottom:12px; transition:background .3s; }
297
+ .wave h4 { color:var(--ink); margin:0 0 6px; font-size:1rem; }
298
+ .wave p { margin:4px 0; }
299
+ .diagram-box { background:linear-gradient(180deg,var(--mini-from),var(--mini-to)); border:1px solid var(--line); border-radius:18px; padding:clamp(16px,2vw,24px); margin-bottom:16px; text-align:center; overflow-x:auto; transition:background .3s; }
300
+ .diagram-box h3 { text-align:center; margin:0 0 16px; color:var(--ink); font-size:1.05rem; }
301
+ .diagram-box img { max-width:100%; height:auto; border-radius:12px; border:1px solid var(--line); }
302
+ .diagram-box .mermaid { text-align:center; }
303
+ ul { margin:10px 0 14px 20px; color:var(--muted); }
304
+ li { margin:6px 0; }
305
+ code { background:rgba(0,224,255,.06); padding:2px 6px; border-radius:6px; font-size:.88rem; color:var(--accent); }
306
+ [data-theme="light"] code { background:rgba(3,105,161,.08); color:#0369A1; }
307
+ a { color:var(--brand-2); text-decoration:none; }
308
+ a:hover { text-decoration:underline; color:var(--accent); }
309
+ .back-btn { position:absolute; top:20px; right:24px; z-index:2; display:inline-flex; align-items:center; gap:7px; padding:8px 16px; background:rgba(0,224,255,.08); border:1px solid rgba(0,224,255,.2); color:var(--accent); border-radius:999px; font-size:.85rem; text-decoration:none; font-weight:500; }
310
+ .back-btn:hover { background:rgba(0,224,255,.18); text-decoration:none; }
311
+ [data-theme="light"] .back-btn { background:rgba(255,255,255,.2); border-color:rgba(255,255,255,.3); color:#fff; }
312
+ [data-theme="light"] .back-btn:hover { background:rgba(255,255,255,.3); }
313
+ /* Theme toggle button */
314
+ .theme-toggle { position:fixed; top:18px; right:68px; z-index:999; width:42px; height:42px; border:none; border-radius:10px; background:rgba(0,224,255,.1); color:var(--accent); cursor:pointer; display:flex; align-items:center; justify-content:center; box-shadow:0 4px 14px rgba(0,224,255,.12); border:1px solid rgba(0,224,255,.2); transition:background .2s; }
315
+ .theme-toggle:hover { background:rgba(0,224,255,.2); }
316
+ [data-theme="light"] .theme-toggle { background:rgba(29,78,216,.08); color:#1D4ED8; box-shadow:0 4px 14px rgba(0,0,0,.08); border-color:rgba(29,78,216,.2); }
317
+ [data-theme="light"] .theme-toggle:hover { background:rgba(29,78,216,.15); }
318
+ .theme-toggle .icon-dark, .theme-toggle .icon-light { font-size:20px; }
319
+ [data-theme="light"] .theme-toggle .icon-dark { display:none; }
320
+ [data-theme="dark"] .theme-toggle .icon-light, :root:not([data-theme]) .theme-toggle .icon-light { display:none; }
321
+ .print-btn { position:fixed; top:18px; right:18px; z-index:999; width:42px; height:42px; border:none; border-radius:10px; background:rgba(0,224,255,.1); color:var(--accent); cursor:pointer; display:flex; align-items:center; justify-content:center; box-shadow:0 4px 14px rgba(0,224,255,.12); border:1px solid rgba(0,224,255,.2); }
322
+ .print-btn:hover { background:rgba(0,224,255,.2); }
323
+ [data-theme="light"] .print-btn { background:rgba(29,78,216,.08); color:#1D4ED8; box-shadow:0 4px 14px rgba(0,0,0,.08); border-color:rgba(29,78,216,.2); }
324
+ [data-theme="light"] .print-btn:hover { background:rgba(29,78,216,.15); }
325
+ .print-btn svg { width:20px; height:20px; fill:currentColor; }
326
+ .footer { text-align:center; padding:24px; opacity:.5; font-size:.85rem; }
327
+ /* bd-wrap for diagrams */
328
+ .bd-wrap { background:linear-gradient(180deg,var(--mini-from),var(--mini-to)); border:1px solid var(--line); border-radius:18px; padding:24px; margin:14px 0; overflow-x:auto; transition:background .3s; }
329
+ .bd-node { text-align:center; border-radius:12px; padding:14px; }
330
+ .bd-sub { display:block; font-size:.85rem; color:var(--muted); margin-top:4px; }
331
+ .bd-root { background:linear-gradient(135deg,#1B3B6F,#2F6DB3); color:#fff; border:1px solid var(--accent); }
332
+ .bd-global { background:var(--ok-bg); color:var(--ok); border:1px solid rgba(34,197,94,.3); }
333
+ .bd-local { background:var(--line); color:var(--ink); border:1px solid var(--brand); }
334
+ .bd-active { box-shadow:0 0 12px rgba(0,224,255,.3); }
335
+ /* Dependency flow diagram */
336
+ .dep-flow { padding:8px 0; }
337
+ .dep-flow-wave { margin-bottom:0; }
338
+ .dep-flow-wave-label { font-size:.78rem; text-transform:uppercase; letter-spacing:.12em; color:var(--muted); font-weight:600; margin-bottom:10px; padding-left:4px; }
339
+ .dep-flow-row { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
340
+ .dep-flow-node { background:linear-gradient(135deg,#1B3B6F,#2F6DB3); border:2px solid #4FB3FF; border-radius:14px; padding:14px 16px; text-align:center; position:relative; transition:transform .2s,box-shadow .2s; }
341
+ .dep-flow-node:hover { transform:translateY(-2px); box-shadow:0 6px 20px rgba(0,224,255,.2); }
342
+ .dep-flow-id { display:block; font-size:.82rem; font-weight:700; color:#00E0FF; letter-spacing:.06em; }
343
+ .dep-flow-name { display:block; font-size:.92rem; font-weight:600; color:#fff; margin-top:4px; line-height:1.3; }
344
+ .dep-flow-dep { display:block; font-size:.72rem; color:rgba(255,255,255,.6); margin-top:6px; font-style:italic; }
345
+ .dep-flow-L { border-color:#F87171; background:linear-gradient(135deg,#7F1D1D,#991B1B); }
346
+ .dep-flow-L .dep-flow-id { color:#FCA5A5; }
347
+ .dep-flow-L::after { content:"L"; position:absolute; top:8px; right:10px; font-size:.65rem; font-weight:700; background:#EF4444; color:#fff; padding:1px 6px; border-radius:4px; }
348
+ .dep-flow-M::after { content:"M"; position:absolute; top:8px; right:10px; font-size:.65rem; font-weight:700; background:#2F6DB3; color:#fff; padding:1px 6px; border-radius:4px; }
349
+ .dep-flow-arrows-down { text-align:center; height:40px; opacity:.7; }
350
+ [data-theme="light"] .dep-flow-node { background:linear-gradient(135deg,#DBEAFE,#EFF6FF); border-color:#2563EB; }
351
+ [data-theme="light"] .dep-flow-id { color:#1D4ED8; }
352
+ [data-theme="light"] .dep-flow-name { color:#1E293B; }
353
+ [data-theme="light"] .dep-flow-dep { color:#64748B; }
354
+ [data-theme="light"] .dep-flow-L { background:linear-gradient(135deg,#FEE2E2,#FEF2F2); border-color:#DC2626; }
355
+ [data-theme="light"] .dep-flow-L .dep-flow-id { color:#DC2626; }
356
+ [data-theme="light"] .dep-flow-L::after { background:#DC2626; }
357
+ [data-theme="light"] .dep-flow-M::after { background:#2563EB; }
358
+ [data-theme="light"] .dep-flow-arrows-down svg line { stroke:#2563EB; }
359
+ [data-theme="light"] .dep-flow-arrows-down svg marker path { fill:#2563EB; }
360
+ @media (max-width:600px) { .page { width:100%; padding:12px 14px 48px; } .layout { grid-template-columns:1fr; } .toc { position:static; } .grid.two,.grid.three { grid-template-columns:1fr; } .dep-flow-row { grid-template-columns:1fr; } }
361
+ @media (min-width:601px) and (max-width:960px) { .layout { grid-template-columns:1fr; } .toc { position:static; } .dep-flow-row { grid-template-columns:repeat(2,1fr); } }
362
+ @media print { .print-btn,.toc,.back-btn,.theme-toggle { display:none!important; } html,body { background:#fff!important; color:#000!important; } .page { max-width:100%!important; padding:0!important; } .layout { display:block!important; } section.card,.epic-card,.diagram-box,.wave,.bd-wrap,.callout,.mini-card { box-shadow:none!important; border:1px solid #ccc!important; border-radius:0!important; background:#fff!important; } .mermaid { page-break-inside:avoid; } }
363
+ `;
364
+ }
365
+ // ============================================================
366
+ // THEME TOGGLE JS
367
+ // ============================================================
368
+ function getThemeToggleJS() {
369
+ return `
370
+ <script>
371
+ (function(){
372
+ const KEY = 'creative-ia50-theme';
373
+ const saved = localStorage.getItem(KEY);
374
+ if (saved) document.documentElement.setAttribute('data-theme', saved);
375
+ window.toggleTheme = function() {
376
+ const current = document.documentElement.getAttribute('data-theme') || 'dark';
377
+ const next = current === 'dark' ? 'light' : 'dark';
378
+ document.documentElement.setAttribute('data-theme', next);
379
+ localStorage.setItem(KEY, next);
380
+ // Re-init mermaid with correct theme + variables
381
+ if (typeof mermaid !== 'undefined') {
382
+ const isLight = next === 'light';
383
+ const vars = isLight ? window.__mermaidLightVars : window.__mermaidDarkVars;
384
+ mermaid.initialize({ startOnLoad: false, theme: isLight ? 'default' : 'dark', themeVariables: vars || {} });
385
+ document.querySelectorAll('.mermaid[data-processed]').forEach(function(el) {
386
+ el.removeAttribute('data-processed');
387
+ el.innerHTML = el.getAttribute('data-original') || el.textContent;
388
+ });
389
+ mermaid.run();
390
+ }
391
+ };
392
+ // Store original mermaid content for re-rendering
393
+ document.addEventListener('DOMContentLoaded', function() {
394
+ document.querySelectorAll('.mermaid').forEach(function(el) {
395
+ el.setAttribute('data-original', el.textContent);
396
+ });
397
+ });
398
+ })();
399
+ </script>`;
400
+ }
401
+ // ============================================================
402
+ // MARKDOWN TABLE PARSER — pipe tables → <table> HTML
403
+ // ============================================================
404
+ function parseMarkdownTable(lines) {
405
+ const tableLines = lines.filter((l) => l.trim().startsWith("|"));
406
+ if (tableLines.length < 2)
407
+ return "";
408
+ const parseRow = (line) => line.split("|").map((c) => c.trim()).filter(Boolean);
409
+ const headers = parseRow(tableLines[0]);
410
+ // skip separator line (|---|---|...)
411
+ const startIdx = tableLines[1].match(/^\|?\s*[-:]+/) ? 2 : 1;
412
+ const rows = tableLines.slice(startIdx).map(parseRow);
413
+ const thHtml = headers.map((h) => `<th>${fmt(h)}</th>`).join("");
414
+ const trHtml = rows
415
+ .filter((r) => r.length > 0 && !r.every((c) => c.match(/^[-:]+$/)))
416
+ .map((r) => {
417
+ const cells = r.map((c) => {
418
+ // size badges
419
+ if (c === "L")
420
+ return `<td><span class="size-badge size-L">L</span></td>`;
421
+ if (c === "M")
422
+ return `<td><span class="size-badge size-M">M</span></td>`;
423
+ // dep-tags for EP-XX references
424
+ const tagged = c.replace(/(EP-\d{2})/g, '<span class="dep-tag">$1</span>');
425
+ return `<td>${fmt(tagged === "—" ? "—" : tagged)}</td>`;
426
+ });
427
+ return `<tr>${cells.join("")}</tr>`;
428
+ })
429
+ .join("\n");
430
+ return `<table><thead><tr>${thHtml}</tr></thead><tbody>${trHtml}</tbody></table>`;
431
+ }
432
+ function parseEpicSection(lines) {
433
+ let objective = "";
434
+ let scope = [];
435
+ let criteria = [];
436
+ let dependencies = [];
437
+ let squads = [];
438
+ let size = "M";
439
+ let currentBlock = "";
440
+ for (const line of lines) {
441
+ const trimmed = line.trim();
442
+ if (!trimmed || trimmed === "---")
443
+ continue;
444
+ if (trimmed.startsWith("**Objetivo:**")) {
445
+ objective = trimmed.replace("**Objetivo:**", "").trim();
446
+ currentBlock = "";
447
+ continue;
448
+ }
449
+ if (trimmed.startsWith("**Escopo:**")) {
450
+ currentBlock = "scope";
451
+ continue;
452
+ }
453
+ if (trimmed.startsWith("**Critérios de aceite:**") || trimmed.startsWith("**Criterios de aceite:**")) {
454
+ currentBlock = "criteria";
455
+ continue;
456
+ }
457
+ if (trimmed.startsWith("**Dependências:**") || trimmed.startsWith("**Dependencias:**")) {
458
+ const val = trimmed.replace(/\*\*Depend[eê]ncias:\*\*/, "").trim();
459
+ if (val && val !== "Nenhuma (épico fundacional)" && val !== "Nenhuma") {
460
+ dependencies = val.split(",").map((d) => d.trim()).filter(Boolean);
461
+ }
462
+ currentBlock = "";
463
+ continue;
464
+ }
465
+ if (trimmed.startsWith("**Squads:**")) {
466
+ const val = trimmed.replace("**Squads:**", "").trim();
467
+ squads = val.split("+").map((s) => s.trim()).filter(Boolean);
468
+ currentBlock = "";
469
+ continue;
470
+ }
471
+ if (trimmed.startsWith("**Estimativa:**")) {
472
+ const val = trimmed.replace("**Estimativa:**", "").trim();
473
+ size = val.includes("L") ? "L" : "M";
474
+ currentBlock = "";
475
+ continue;
476
+ }
477
+ if (trimmed.startsWith("- ") && currentBlock === "scope") {
478
+ scope.push(trimmed.slice(2));
479
+ }
480
+ else if (trimmed.startsWith("- [ ]") && currentBlock === "criteria") {
481
+ criteria.push(trimmed.slice(6).trim());
482
+ }
483
+ else if (trimmed.startsWith("- ") && currentBlock === "criteria") {
484
+ criteria.push(trimmed.slice(2));
485
+ }
486
+ }
487
+ return { id: "", title: "", objective, scope, criteria, dependencies, squads, size };
488
+ }
489
+ function renderEpicCard(epic, idx) {
490
+ const scopeHtml = epic.scope.length > 0
491
+ ? `<h4>Escopo</h4><ul class="scope-list">${epic.scope.map((s) => `<li>${fmt(s)}</li>`).join("")}</ul>`
492
+ : "";
493
+ const criteriaHtml = epic.criteria.length > 0
494
+ ? `<h4>Critérios de Aceite</h4><ul class="criteria-list">${epic.criteria.map((c) => `<li>${fmt(c)}</li>`).join("")}</ul>`
495
+ : "";
496
+ const depsHtml = epic.dependencies.length > 0
497
+ ? epic.dependencies.map((d) => `<span class="dep-tag">${d}</span>`).join("")
498
+ : '<span style="color:var(--muted);font-size:.85rem;">Nenhuma (épico fundacional)</span>';
499
+ const squadsHtml = epic.squads.map((s) => `<span class="squad-tag">${s}</span>`).join("");
500
+ const sizeCls = epic.size === "L" ? "size-L" : "size-M";
501
+ return `<div class="epic-card" id="epic-${idx}">
502
+ <div class="epic-header">
503
+ <h3>${epic.id} — ${epic.title}</h3>
504
+ <span class="size-badge ${sizeCls}">${epic.size === "L" ? "L (grande)" : "M (médio)"}</span>
505
+ </div>
506
+ <p class="lead"><strong>Objetivo:</strong> ${fmt(epic.objective)}</p>
507
+ ${scopeHtml}
508
+ ${criteriaHtml}
509
+ <div style="display:flex;flex-wrap:wrap;gap:12px;margin-top:14px;align-items:center;">
510
+ <div><strong style="font-size:.85rem;color:var(--muted);">Dependências:</strong> ${depsHtml}</div>
511
+ <div style="margin-left:auto;"><strong style="font-size:.85rem;color:var(--muted);">Squads:</strong> ${squadsHtml}</div>
512
+ </div>
513
+ </div>`;
514
+ }
515
+ // ============================================================
516
+ // WAVE RENDERER — Ondas de entrega
517
+ // ============================================================
518
+ function renderWaves(lines) {
519
+ const waves = [];
520
+ let current = null;
521
+ for (const line of lines) {
522
+ const trimmed = line.trim();
523
+ if (trimmed.startsWith("### ")) {
524
+ if (current)
525
+ waves.push(current);
526
+ current = { title: trimmed.replace("### ", ""), desc: "" };
527
+ }
528
+ else if (current && trimmed) {
529
+ current.desc += (current.desc ? " " : "") + trimmed;
530
+ }
531
+ }
532
+ if (current)
533
+ waves.push(current);
534
+ return waves.map((w) => `<div class="wave">
535
+ <h4><span class="material-symbols-outlined" style="font-size:1rem;vertical-align:middle;margin-right:4px;">waves</span> ${w.title}</h4>
536
+ <p>${fmt(w.desc)}</p>
537
+ </div>`).join("");
538
+ }
539
+ // ============================================================
540
+ // MERMAID SEQUENCE DIAGRAMS — PIX journeys
541
+ // ============================================================
542
+ function getMermaidSequenceDiagrams() {
543
+ return {
544
+ "Enviar PIX": `sequenceDiagram
545
+ participant U as 👤 Usuário
546
+ participant App as 📱 App
547
+ participant BFF as 🔀 BFF
548
+ participant Core as ⚙️ Core Banking
549
+ participant DICT as 📖 DICT/BACEN
550
+ participant SPI as 🏦 SPI/BACEN
551
+ U->>App: Informa chave/dados
552
+ App->>BFF: POST /pix/transfer
553
+ BFF->>DICT: Consulta chave
554
+ DICT-->>BFF: Dados do recebedor
555
+ BFF-->>App: Confirma dados
556
+ U->>App: Autoriza (senha/bio)
557
+ App->>BFF: Confirma transferência
558
+ BFF->>Core: Debita conta
559
+ Core->>SPI: PACS.008 (ordem)
560
+ SPI-->>Core: PACS.002 (confirmação)
561
+ Core-->>BFF: Sucesso + endToEndId
562
+ BFF-->>App: Recibo
563
+ App-->>U: ✅ PIX enviado`,
564
+ "Receber PIX": `sequenceDiagram
565
+ participant SPI as 🏦 SPI/BACEN
566
+ participant Core as ⚙️ Core Banking
567
+ participant BFF as 🔀 BFF
568
+ participant Push as 🔔 Push Service
569
+ participant App as 📱 App
570
+ participant U as 👤 Usuário
571
+ SPI->>Core: PACS.008 (crédito)
572
+ Core->>Core: Credita conta
573
+ Core-->>SPI: PACS.002 (confirmação)
574
+ Core->>BFF: Evento: PIX recebido
575
+ BFF->>Push: Notificação
576
+ Push-->>App: Push notification
577
+ App-->>U: ✅ PIX recebido (< 3s)`,
578
+ "Chaves PIX": `sequenceDiagram
579
+ participant U as 👤 Usuário
580
+ participant App as 📱 App
581
+ participant BFF as 🔀 BFF
582
+ participant Core as ⚙️ Core Banking
583
+ participant DICT as 📖 DICT/BACEN
584
+ U->>App: Cadastrar chave
585
+ App->>BFF: POST /pix/keys
586
+ BFF->>Core: Valida cliente
587
+ Core->>DICT: CreateEntry
588
+ DICT-->>Core: Confirmação
589
+ Note over DICT,Core: Se e-mail/telefone: OTP
590
+ Core-->>BFF: Chave criada
591
+ BFF-->>App: Sucesso
592
+ App-->>U: ✅ Chave cadastrada`,
593
+ "QR Code PIX": `sequenceDiagram
594
+ participant U as 👤 Usuário
595
+ participant App as 📱 App
596
+ participant BFF as 🔀 BFF
597
+ participant Core as ⚙️ Core Banking
598
+ U->>App: Gerar QR Code
599
+ App->>BFF: POST /pix/qrcode
600
+ BFF->>Core: Gera payload EMV
601
+ Core-->>BFF: BR Code + imagem
602
+ BFF-->>App: QR Code
603
+ App-->>U: 📷 QR Code gerado
604
+ Note over U,App: Compartilha via WhatsApp/Telegram
605
+ U->>App: Ler QR Code (câmera)
606
+ App->>App: Parse BR Code
607
+ App->>BFF: POST /pix/transfer
608
+ Note over App,BFF: Segue fluxo Enviar PIX`
609
+ };
610
+ }
611
+ // ============================================================
612
+ // EPICS REPORT RENDERER — Full épicos HTML generation
613
+ // ============================================================
614
+ function renderEpicsReport(sections, diagramsDir) {
615
+ let html = "";
616
+ let epicIdx = 0;
617
+ for (let i = 0; i < sections.length; i++) {
618
+ const s = sections[i];
619
+ const titleLower = s.title.toLowerCase();
620
+ const id = `s${i}`;
621
+ const icon = getIcon(s.title);
622
+ // === VISÃO GERAL section ===
623
+ if (titleLower.includes("visão geral") || titleLower.includes("visao geral")) {
624
+ html += `<section class="card" id="${id}">
625
+ <h2><span class="material-symbols-outlined">${icon}</span> ${s.title}</h2>
626
+ <p class="lead">${s.lines.filter((l) => l.trim() && !l.startsWith("\`\`\`") && !l.includes("──") && !l.includes("├") && !l.includes("└") && !l.includes("│")).map((l) => fmt(l.trim())).join(" ")}</p>
627
+ <div class="diagram-box">
628
+ <h3>Fluxo de Dependências entre Épicos</h3>
629
+ <div class="dep-flow">
630
+ <div class="dep-flow-wave">
631
+ <div class="dep-flow-wave-label">Onda 1 — Fundação</div>
632
+ <div class="dep-flow-row">
633
+ <div class="dep-flow-node dep-flow-L" id="df-ep01">
634
+ <span class="dep-flow-id">EP-01</span>
635
+ <span class="dep-flow-name">Infraestrutura BACEN/SPI</span>
636
+ </div>
637
+ <div class="dep-flow-node dep-flow-M" id="df-ep08">
638
+ <span class="dep-flow-id">EP-08</span>
639
+ <span class="dep-flow-name">Observabilidade</span>
640
+ </div>
641
+ </div>
642
+ </div>
643
+ <div class="dep-flow-arrows-down">
644
+ <svg width="100%" height="40" aria-hidden="true"><line x1="25%" y1="0" x2="20%" y2="40" stroke="var(--accent)" stroke-width="2" marker-end="url(#arrowD)"/><line x1="25%" y1="0" x2="50%" y2="40" stroke="var(--accent)" stroke-width="2" marker-end="url(#arrowD)"/><line x1="25%" y1="0" x2="80%" y2="40" stroke="var(--accent)" stroke-width="2" marker-end="url(#arrowD)"/><defs><marker id="arrowD" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="var(--accent)"/></marker></defs></svg>
645
+ </div>
646
+ <div class="dep-flow-wave">
647
+ <div class="dep-flow-wave-label">Onda 2 — MVP PIX</div>
648
+ <div class="dep-flow-row">
649
+ <div class="dep-flow-node dep-flow-M" id="df-ep02">
650
+ <span class="dep-flow-id">EP-02</span>
651
+ <span class="dep-flow-name">Chaves PIX</span>
652
+ <span class="dep-flow-dep">← EP-01</span>
653
+ </div>
654
+ <div class="dep-flow-node dep-flow-L" id="df-ep03">
655
+ <span class="dep-flow-id">EP-03</span>
656
+ <span class="dep-flow-name">Enviar PIX</span>
657
+ <span class="dep-flow-dep">← EP-01, EP-02</span>
658
+ </div>
659
+ <div class="dep-flow-node dep-flow-M" id="df-ep04">
660
+ <span class="dep-flow-id">EP-04</span>
661
+ <span class="dep-flow-name">Receber PIX</span>
662
+ <span class="dep-flow-dep">← EP-01</span>
663
+ </div>
664
+ </div>
665
+ </div>
666
+ <div class="dep-flow-arrows-down">
667
+ <svg width="100%" height="40" aria-hidden="true"><line x1="33%" y1="0" x2="20%" y2="40" stroke="var(--accent)" stroke-width="2" marker-end="url(#arrowD2)"/><line x1="50%" y1="0" x2="50%" y2="40" stroke="var(--accent)" stroke-width="2" marker-end="url(#arrowD2)"/><line x1="66%" y1="0" x2="80%" y2="40" stroke="var(--accent)" stroke-width="2" marker-end="url(#arrowD2)"/><defs><marker id="arrowD2" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="var(--accent)"/></marker></defs></svg>
668
+ </div>
669
+ <div class="dep-flow-wave">
670
+ <div class="dep-flow-wave-label">Onda 3 — Experiência Completa</div>
671
+ <div class="dep-flow-row">
672
+ <div class="dep-flow-node dep-flow-M" id="df-ep05">
673
+ <span class="dep-flow-id">EP-05</span>
674
+ <span class="dep-flow-name">QR Code</span>
675
+ <span class="dep-flow-dep">← EP-03, EP-04</span>
676
+ </div>
677
+ <div class="dep-flow-node dep-flow-M" id="df-ep06">
678
+ <span class="dep-flow-id">EP-06</span>
679
+ <span class="dep-flow-name">Agendado + Devolução</span>
680
+ <span class="dep-flow-dep">← EP-03, EP-04</span>
681
+ </div>
682
+ <div class="dep-flow-node dep-flow-M" id="df-ep07">
683
+ <span class="dep-flow-id">EP-07</span>
684
+ <span class="dep-flow-name">Extrato + Limites</span>
685
+ <span class="dep-flow-dep">← EP-03, EP-04, EP-06</span>
686
+ </div>
687
+ </div>
688
+ </div>
689
+ </div>
690
+ </div>
691
+ ${diagramsDir ? `<div class="diagram-box">
692
+ <h3><span class="material-symbols-outlined" style="vertical-align:middle;margin-right:6px;">cloud</span> Arquitetura AWS</h3>
693
+ <img src="${diagramsDir}/pix-aws-architecture.png" alt="Arquitetura AWS PIX" loading="lazy" />
694
+ </div>` : ""}
695
+ </section>`;
696
+ continue;
697
+ }
698
+ // === EP-XX sections → epic-card ===
699
+ if (s.title.match(/^EP-\d{2}/)) {
700
+ const epMatch = s.title.match(/^(EP-\d{2})\s*[—-]\s*(.+)/);
701
+ const parsed = parseEpicSection(s.lines);
702
+ if (parsed && epMatch) {
703
+ parsed.id = epMatch[1];
704
+ parsed.title = epMatch[2];
705
+ epicIdx++;
706
+ // Check if this epic has a matching PNG diagram
707
+ let diagramHtml = "";
708
+ const epId = epMatch[1].replace("EP-", "");
709
+ const diagramMap = {
710
+ "03": { file: "pix-flow-enviar.png", label: "Fluxo Enviar PIX" },
711
+ "04": { file: "pix-flow-receber.png", label: "Fluxo Receber PIX" },
712
+ "02": { file: "pix-flow-chaves.png", label: "Fluxo Chaves PIX" },
713
+ "05": { file: "pix-flow-qrcode.png", label: "Fluxo QR Code PIX" },
714
+ };
715
+ if (diagramsDir && diagramMap[epId]) {
716
+ diagramHtml += `<div class="diagram-box" style="margin-top:16px;">
717
+ <h3><span class="material-symbols-outlined" style="vertical-align:middle;margin-right:6px;">schema</span> ${diagramMap[epId].label} (AWS)</h3>
718
+ <img src="${diagramsDir}/${diagramMap[epId].file}" alt="${diagramMap[epId].label}" loading="lazy" />
719
+ </div>`;
720
+ }
721
+ // Mermaid sequence diagram for matching journeys
722
+ const seqDiagrams = getMermaidSequenceDiagrams();
723
+ const seqMap = {
724
+ "03": "Enviar PIX",
725
+ "04": "Receber PIX",
726
+ "02": "Chaves PIX",
727
+ "05": "QR Code PIX",
728
+ };
729
+ if (seqMap[epId] && seqDiagrams[seqMap[epId]]) {
730
+ diagramHtml += `<div class="diagram-box" style="margin-top:16px;">
731
+ <h3><span class="material-symbols-outlined" style="vertical-align:middle;margin-right:6px;">route</span> Diagrama de Sequência — ${seqMap[epId]}</h3>
732
+ <div class="mermaid">
733
+ ${seqDiagrams[seqMap[epId]]}
734
+ </div>
735
+ </div>`;
736
+ }
737
+ html += `<section class="card" id="${id}">
738
+ <h2><span class="material-symbols-outlined">${icon}</span> ${s.title}</h2>
739
+ ${renderEpicCard(parsed, epicIdx)}
740
+ ${diagramHtml}
741
+ </section>`;
742
+ continue;
743
+ }
744
+ }
745
+ // === RESUMO DE ESTIMATIVAS section → table ===
746
+ if (titleLower.includes("resumo")) {
747
+ const tableHtml = parseMarkdownTable(s.lines);
748
+ const extraLines = s.lines.filter((l) => !l.trim().startsWith("|") && l.trim() && !l.trim().match(/^\|?\s*[-:]+/));
749
+ const extraHtml = extraLines.map((l) => `<p>${fmt(l)}</p>`).join("\n");
750
+ html += `<section class="card" id="${id}">
751
+ <h2><span class="material-symbols-outlined">${icon}</span> ${s.title}</h2>
752
+ ${tableHtml}
753
+ ${extraHtml}
754
+ </section>`;
755
+ continue;
756
+ }
757
+ // === ONDAS DE ENTREGA section → wave components ===
758
+ if (titleLower.includes("onda")) {
759
+ const wavesHtml = renderWaves(s.lines);
760
+ html += `<section class="card" id="${id}">
761
+ <h2><span class="material-symbols-outlined">${icon}</span> ${s.title}</h2>
762
+ ${wavesHtml}
763
+ </section>`;
764
+ continue;
765
+ }
766
+ // === DEFAULT fallback ===
767
+ html += renderSection(s, i, sections);
768
+ }
769
+ return html;
770
+ }
771
+ // ============================================================
772
+ // MAIN EXPORT — generateReport
773
+ // ============================================================
774
+ export async function generateReport(params) {
775
+ const { title, content, reportType, outputDir, diagramsDir } = params;
776
+ const now = new Date();
777
+ const dateStr = now.toISOString().split("T")[0];
778
+ const slug = title.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "")
779
+ .replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
780
+ const filename = `${dateStr}-${reportType}-${slug}.html`;
781
+ const dir = outputDir || join(homedir(), "creative-reports");
782
+ await mkdir(dir, { recursive: true });
783
+ const filepath = join(dir, filename);
784
+ // Try to load extra CSS from S3 (optional enhancement)
785
+ let extraCSS = "";
786
+ try {
787
+ extraCSS = await loadTemplate("_base-styles.css");
788
+ }
789
+ catch {
790
+ // No extra CSS — dual theme CSS is self-contained
791
+ }
792
+ const sections = parseSections(content);
793
+ const isEpics = reportType === "epics";
794
+ const sectionsHtml = isEpics
795
+ ? renderEpicsReport(sections, diagramsDir)
796
+ : sections.map((s, i) => renderSection(s, i, sections)).join("\n");
797
+ const tocHtml = renderToc(sections);
798
+ const reportTypeLabels = {
799
+ feasibility: "Parecer de Viabilidade",
800
+ architecture: "Arquitetura",
801
+ analysis: "Analise de Codigo",
802
+ process: "Processo",
803
+ epics: "Epicos de Entrega",
804
+ };
805
+ const hasMermaid = isEpics || content.includes("```mermaid") || content.includes("class=\"mermaid\"");
806
+ const mermaidScript = hasMermaid
807
+ ? `<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
808
+ <script>
809
+ (function(){
810
+ var darkVars={primaryColor:'#1B3B6F',primaryTextColor:'#E5E7EB',primaryBorderColor:'#2F6DB3',lineColor:'#4FB3FF',secondaryColor:'#0F1C2E',tertiaryColor:'#111827',noteBkgColor:'#1B3B6F',noteTextColor:'#E5E7EB',noteBorderColor:'#2F6DB3',actorBkg:'#1B3B6F',actorTextColor:'#E5E7EB',actorBorder:'#2F6DB3',signalColor:'#4FB3FF',signalTextColor:'#E5E7EB',labelBoxBkgColor:'#0F1C2E',labelTextColor:'#E5E7EB'};
811
+ var lightVars={primaryColor:'#DBEAFE',primaryTextColor:'#1E293B',primaryBorderColor:'#2563EB',lineColor:'#1D4ED8',secondaryColor:'#EFF6FF',tertiaryColor:'#F8FAFC',noteBkgColor:'#DBEAFE',noteTextColor:'#1E293B',noteBorderColor:'#2563EB',actorBkg:'#DBEAFE',actorTextColor:'#1E293B',actorBorder:'#2563EB',signalColor:'#1D4ED8',signalTextColor:'#1E293B',labelBoxBkgColor:'#EFF6FF',labelTextColor:'#1E293B'};
812
+ var saved=localStorage.getItem('creative-ia50-theme')||'dark';
813
+ var isL=saved==='light';
814
+ mermaid.initialize({startOnLoad:true,theme:isL?'default':'dark',themeVariables:isL?lightVars:darkVars});
815
+ window.__mermaidDarkVars=darkVars;
816
+ window.__mermaidLightVars=lightVars;
817
+ })();
818
+ </script>`
819
+ : "";
820
+ const html = `<!DOCTYPE html>
821
+ <html lang="pt-BR">
822
+ <head>
823
+ <meta charset="UTF-8" />
824
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
825
+ <title>${title} — Creative IA 50</title>
826
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&display=swap" />
827
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
828
+ ${mermaidScript}
829
+ <style>
830
+ ${getDualThemeCSS()}
831
+ ${extraCSS}
832
+ </style>
833
+ </head>
834
+ <body>
835
+ ${getThemeToggleJS()}
836
+ <a href="#main-content" class="skip-link" style="position:absolute;left:-9999px;top:0;z-index:9999;padding:8px 16px;background:#4FB3FF;color:#000;font-weight:600;text-decoration:none;border-radius:0 0 4px 0;" onfocus="this.style.left='0'" onblur="this.style.left='-9999px'">Pular para conteudo</a>
837
+
838
+ <button class="theme-toggle" onclick="toggleTheme()" title="Alternar tema" aria-label="Alternar tema claro/escuro">
839
+ <span class="material-symbols-outlined icon-dark">light_mode</span>
840
+ <span class="material-symbols-outlined icon-light">dark_mode</span>
841
+ </button>
842
+ <button class="print-btn" onclick="window.print()" title="Imprimir" aria-label="Imprimir">
843
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 8a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1z"/><path d="M5 1a1 1 0 0 0-1 1v1H3.5A1.5 1.5 0 0 0 2 4.5v3A1.5 1.5 0 0 0 3.5 9H4v2.5a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V9h.5A1.5 1.5 0 0 0 14 7.5v-3A1.5 1.5 0 0 0 12.5 3H12V2a1 1 0 0 0-1-1H5zm6 1v1H5V2h6zm-6 7v3h6V9H5z"/></svg>
844
+ </button>
845
+
846
+ <div class="page">
847
+ <header class="cover">
848
+ <div class="brand">
849
+ <img src="${LOGO_BASE64}" alt="" role="presentation" class="brand-logo" />
850
+ <div>
851
+ <div class="eyebrow">Creative IA 50 · ${reportTypeLabels[reportType] || reportType}</div>
852
+ <h1>${title}</h1>
853
+ <p class="subtitle">Documento gerado automaticamente via MCP Server</p>
854
+ </div>
855
+ </div>
856
+ <div class="cover-meta">
857
+ <span class="pill" style="background:linear-gradient(135deg,#2F6DB3,#4FB3FF);color:#fff;font-weight:600">${reportType.toUpperCase()}</span>
858
+ <span class="pill">${dateStr}</span>
859
+ <span class="pill">MCP Server v1.0.0</span>
860
+ </div>
861
+ </header>
862
+
863
+ <div class="layout">
864
+ <aside class="toc" role="navigation" aria-label="Sumario">
865
+ <h2><span class="material-symbols-outlined" style="font-size:16px;vertical-align:middle">menu_book</span> Sumario</h2>
866
+ ${tocHtml}
867
+ </aside>
868
+
869
+ <main class="content" id="main-content">
870
+ ${sectionsHtml}
871
+ </main>
872
+ </div>
873
+
874
+ <div class="footer">
875
+ Creative IA 50 &copy; ${now.getFullYear()} — Propriedade intelectual protegida. Distribuicao nao autorizada e proibida.
876
+ </div>
877
+ </div>
878
+
879
+ </body>
880
+ </html>`;
881
+ await writeFile(filepath, html, "utf-8");
882
+ return [
883
+ `Report generated successfully.`,
884
+ ``,
885
+ `File: ${filepath}`,
886
+ `Type: ${reportType}`,
887
+ `Size: ${Buffer.byteLength(html, "utf-8")} bytes`,
888
+ ``,
889
+ `Open in browser: file://${filepath}`,
890
+ ].join("\n");
891
+ }