onboarding-kit 0.0.0-stage → 0.2.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.
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.escapeHtml = void 0;
4
+ exports.renderMissingPage = renderMissingPage;
5
+ exports.renderErrorPage = renderErrorPage;
6
+ exports.renderOnboarding = renderOnboarding;
7
+ const brand_1 = require("../brand");
8
+ const load_1 = require("../load");
9
+ const template_1 = require("../template");
10
+ const assets_1 = require("./assets");
11
+ const copy_1 = require("./copy");
12
+ const html_1 = require("./html");
13
+ const slides_1 = require("./slides");
14
+ var html_2 = require("./html");
15
+ Object.defineProperty(exports, "escapeHtml", { enumerable: true, get: function () { return html_2.escapeHtml; } });
16
+ const ICON_GRID = '<svg viewBox="0 0 20 20" aria-hidden="true"><path fill="currentColor" d="M3 3h5.5v5.5H3zM11.5 3H17v5.5h-5.5zM3 11.5h5.5V17H3zM11.5 11.5H17V17h-5.5z" opacity=".9"/></svg>';
17
+ const ICON_THEME = '<svg viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="7" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M10 3a7 7 0 0 1 0 14z" fill="currentColor"/></svg>';
18
+ const HUE_OFFSETS = [0, 18, -18, 36, -36];
19
+ /**
20
+ * Tema a partir das cores da marca. Sem cores, mantém a paleta própria do kit.
21
+ * Com uma cor, os capítulos variam levemente em torno dela; com várias, se revezam entre elas.
22
+ * Marcas sem cor (preto/cinza) viram um tema monocromático.
23
+ */
24
+ function themeFor(brand) {
25
+ const chromatic = brand.colors.filter((c) => !(0, brand_1.isAchromatic)(c)).map((c) => ({ ...c, h: Math.round(c.h) }));
26
+ const ref = chromatic[0] ?? (brand.colors[0] && { ...brand.colors[0], h: Math.round(brand.colors[0].h) });
27
+ if (!ref)
28
+ return { brand, hues: copy_1.CHAPTER_HUES, hue: copy_1.CHAPTER_HUES.welcome, css: "", mono: false };
29
+ const mono = chromatic.length === 0;
30
+ const hues = { ...copy_1.CHAPTER_HUES };
31
+ hues.welcome = hues.finale = ref.h;
32
+ slides_1.CHAPTERS.forEach(([id], i) => {
33
+ hues[id] = chromatic.length > 1 ? chromatic[i % chromatic.length].h : (ref.h + HUE_OFFSETS[i % HUE_OFFSETS.length] + 360) % 360;
34
+ });
35
+ const k = mono ? 0 : Math.round(Math.min(1, ref.s / 55) * 100) / 100;
36
+ const h2 = chromatic[1]?.h ?? (ref.h + 28) % 360;
37
+ const css = `:root{--brand-h:${ref.h};--h2:${h2};--k:${k};--brand-c:hsl(${ref.h} calc(var(--as) * ${k}) var(--al));--brand-on:var(--on-accent)}`;
38
+ return { brand, hues, hue: ref.h, css, mono };
39
+ }
40
+ function shell(lang, title, body, theme, opts = {}) {
41
+ const palette = theme.brand.colors.map(brand_1.toHex).join(" ");
42
+ return `<!doctype html>
43
+ <html lang="${(0, html_1.escapeHtml)(lang)}"${theme.mono ? " data-mono" : ""}${palette ? ` data-palette="${palette}"` : ""}>
44
+ <head>
45
+ <meta charset="utf-8">
46
+ <meta name="viewport" content="width=device-width, initial-scale=1">
47
+ <meta name="robots" content="noindex">
48
+ <title>${(0, html_1.escapeHtml)(title)}</title>
49
+ ${theme.brand.favicon ? `<link rel="icon" href="${(0, html_1.escapeHtml)(theme.brand.favicon)}">` : ""}
50
+ ${theme.brand.colors[0] ? `<meta name="theme-color" content="${(0, brand_1.toHex)(theme.brand.colors[0])}">` : ""}
51
+ ${assets_1.FONTS}
52
+ <style>${assets_1.CSS}${theme.css}</style>
53
+ </head>
54
+ <body${opts.solo ? ' class="solo"' : ""}${opts.state ? ` data-ok-state="${opts.state}"` : ""}>
55
+ <div class="backdrop" id="ok-backdrop" style="--bh:${theme.hue}" aria-hidden="true"><i></i><i></i><i></i></div>
56
+ ${body}
57
+ <script>${assets_1.JS}</script>
58
+ ${opts.mermaid ? `<script src="${assets_1.MERMAID_URL}" onload="window.__okMermaid&&window.__okMermaid()"></script>` : ""}
59
+ </body>
60
+ </html>`;
61
+ }
62
+ function logoImg(theme, cls) {
63
+ return theme.brand.logo ? `<span class="${cls}"><img src="${(0, html_1.escapeHtml)(theme.brand.logo)}" alt=""></span>` : "";
64
+ }
65
+ function soloHead(theme, name, heading, body) {
66
+ const kicker = `<p class="kicker"><span class="num">👋</span>${(0, html_1.escapeHtml)(name ?? "onboarding-kit")}</p>`;
67
+ return `${logoImg(theme, "solo-logo")}${kicker}<h1>${(0, html_1.escapeHtml)(heading)}</h1>${body ? `<p>${(0, html_1.escapeHtml)(body)}</p>` : ""}`;
68
+ }
69
+ function promptBlock(t, prompt) {
70
+ return `<ol class="solo-steps">${t.pending.steps.map((step, i) => `<li><b>${i + 1}</b><span>${(0, html_1.escapeHtml)(step)}</span></li>`).join("")}</ol>
71
+ <div class="prompt-box"><pre class="prompt-text">${(0, html_1.escapeHtml)(prompt)}</pre><button type="button" class="cta copy-cta" data-copy="${(0, html_1.escapeHtml)(prompt)}" data-done="✓ ${(0, html_1.escapeHtml)(t.pending.copied)}">${(0, html_1.escapeHtml)(t.pending.copy)}</button></div>`;
72
+ }
73
+ function waiting(t) {
74
+ return `<p class="waiting" aria-live="polite"><span class="pulse" aria-hidden="true"></span>${(0, html_1.escapeHtml)(t.pending.waiting)}</p>`;
75
+ }
76
+ /** Seções do JSON acompanhadas na tela de espera enquanto a IA preenche. */
77
+ const PROGRESS_SECTIONS = ["project", "business", "features", "domain", "stack", "architecture", "gettingStarted", "conventions", "tours", "gotchas", "decisions", "firstTasks", "brand"];
78
+ function progressBlock(data, t) {
79
+ const items = PROGRESS_SECTIONS.map((key) => ({ key, done: !(0, load_1.isBlank)(data[key]) }));
80
+ const done = items.filter((i) => i.done).length;
81
+ return `<div class="progress" id="ok-progress"><p class="mini-label">${(0, html_1.escapeHtml)(t.pending.progress.replace("{done}", String(done)).replace("{total}", String(items.length)))}</p><div class="progress-bar"><span style="width:${Math.round((done / items.length) * 100)}%"></span></div><ul class="progress-list">${items
82
+ .map((i) => `<li data-key="${i.key}"${i.done ? ' class="is-done"' : ""}>${(0, html_1.escapeHtml)(t.pending.sections[i.key])}</li>`)
83
+ .join("")}</ul></div>`;
84
+ }
85
+ function soloPage(lang, title, inner, theme, state) {
86
+ return shell(lang, title, `<div class="solo-wrap" style="--h:${theme.hue}"><div class="solo-card">${inner}</div></div>`, theme, { solo: true, state });
87
+ }
88
+ function renderMissingPage(root) {
89
+ const t = (0, copy_1.copyFor)("pt-BR");
90
+ const theme = themeFor((0, brand_1.resolveBrand)(undefined, root));
91
+ const name = (0, brand_1.packageName)(root);
92
+ const inner = `${soloHead(theme, name, t.missing.title, t.missing.body)}${promptBlock(t, (0, template_1.aiPrompt)(undefined, { init: true }))}${waiting(t)}<p class="manual">${(0, html_1.escapeHtml)(t.missing.manual)} <code>npx onboarding-kit init</code></p>`;
93
+ return soloPage("pt-BR", name ? `${name} · Onboarding` : "Onboarding", inner, theme, "missing");
94
+ }
95
+ function renderErrorPage(message, root) {
96
+ const t = (0, copy_1.copyFor)("pt-BR");
97
+ const theme = themeFor((0, brand_1.resolveBrand)(undefined, root));
98
+ const name = (0, brand_1.packageName)(root);
99
+ const inner = `${soloHead(theme, name, t.invalid.title, t.invalid.body)}<pre class="error-text">${(0, html_1.escapeHtml)(message)}</pre>${waiting(t)}`;
100
+ return soloPage("pt-BR", "Onboarding", inner, theme, "error");
101
+ }
102
+ function renderPendingPage(data, root, lang, t) {
103
+ const theme = themeFor((0, brand_1.resolveBrand)(data, root));
104
+ const name = data.project?.name?.trim() || (0, brand_1.packageName)(root);
105
+ const inner = `${soloHead(theme, name, t.pending.title, t.pending.body)}${promptBlock(t, (0, template_1.aiPrompt)())}${waiting(t)}${progressBlock(data, t)}`;
106
+ return soloPage(lang, name ? `${name} · Onboarding` : "Onboarding", inner, theme, "pending");
107
+ }
108
+ function welcomeSlide(data, t, chapters, minutes, theme) {
109
+ const p = data.project;
110
+ const name = p?.name?.trim() || "Onboarding";
111
+ const generated = data.meta?.generatedAt ? new Date(data.meta.generatedAt) : undefined;
112
+ const base = (0, html_1.repoBase)(data);
113
+ const meta = [
114
+ p?.status ? `<span class="badge">${(0, html_1.escapeHtml)(p.status)}</span>` : "",
115
+ `<span>⏱ ${minutes} ${t.ui.minutes}</span>`,
116
+ generated && !Number.isNaN(generated.getTime())
117
+ ? `<span>${t.ui.generated} ${(0, html_1.escapeHtml)(generated.toLocaleDateString(data.meta.language || "pt-BR"))}${data.meta.generatedBy ? ` ${t.ui.by} ${(0, html_1.escapeHtml)(data.meta.generatedBy)}` : ""}</span>`
118
+ : "",
119
+ base ? `<a href="${(0, html_1.escapeHtml)(base)}" target="_blank" rel="noopener">${(0, html_1.escapeHtml)(base.replace(/^https?:\/\//, ""))}</a>` : "",
120
+ ].filter(Boolean);
121
+ const journey = chapters
122
+ .filter((c) => c.id !== "welcome" && c.id !== "finale")
123
+ .map((c) => `<li><button type="button" data-go="${c.start}" style="--ch:${theme.hues[c.id]}"><span class="n">${c.num}</span>${(0, html_1.escapeHtml)(c.name)}</button></li>`)
124
+ .join("");
125
+ return {
126
+ chapter: "welcome",
127
+ kind: "welcome",
128
+ title: t.chapters.welcome,
129
+ html: `<div class="welcome">
130
+ ${logoImg(theme, "brand-logo")}
131
+ <p class="hi"><span class="wave" aria-hidden="true">👋</span>${(0, html_1.escapeHtml)(t.welcome.hi)}</p>
132
+ <h1 class="title">${(0, html_1.escapeHtml)(t.welcome.title("")).trim()} <span class="grad">${(0, html_1.escapeHtml)(name)}</span></h1>
133
+ ${p?.tagline ? `<p class="tagline">${(0, html_1.text)(p.tagline)}</p>` : ""}
134
+ <p class="lede">${(0, html_1.escapeHtml)(t.welcome.intro(minutes))}</p>
135
+ <button type="button" class="cta" data-next>${(0, html_1.escapeHtml)(t.welcome.start)} <span class="arrow" aria-hidden="true">→</span></button>
136
+ <div class="meta">${meta.join("")}</div>
137
+ ${journey ? `<div class="journey-wrap"><p class="mini-label">${(0, html_1.escapeHtml)(t.welcome.journey)}</p><ul class="journey">${journey}</ul></div>` : ""}
138
+ </div>`,
139
+ };
140
+ }
141
+ function finaleSlide(t, chapters, hasTasks, theme) {
142
+ const recap = chapters
143
+ .filter((c) => c.id !== "welcome" && c.id !== "finale")
144
+ .map((c) => `<li style="--ch:${theme.hues[c.id]}">${(0, html_1.escapeHtml)(c.name)}</li>`)
145
+ .join("");
146
+ return {
147
+ chapter: "finale",
148
+ kind: "finale",
149
+ title: t.finale.title,
150
+ html: `<div class="finale">
151
+ <span class="party" aria-hidden="true">🎉</span>
152
+ <h1 class="title">${(0, html_1.escapeHtml)(t.finale.title)}</h1>
153
+ <p class="lede">${(0, html_1.escapeHtml)(t.finale.lede)}</p>
154
+ ${recap ? `<p class="mini-label">${(0, html_1.escapeHtml)(t.finale.recap)}</p><ul class="recap">${recap}</ul>` : ""}
155
+ ${hasTasks ? `<p class="task-count" data-task-count data-template="${(0, html_1.escapeHtml)(t.finale.tasks)}"></p>` : ""}
156
+ <div class="actions"><button type="button" class="cta" data-go="0">↺ ${(0, html_1.escapeHtml)(t.finale.restart)}</button><button type="button" class="ghost" data-overview>${(0, html_1.escapeHtml)(t.finale.index)}</button></div>
157
+ </div>`,
158
+ };
159
+ }
160
+ function renderOnboarding(data, options = {}) {
161
+ const lang = data.meta?.language || "pt-BR";
162
+ const t = (0, copy_1.copyFor)(lang);
163
+ const name = data.project?.name?.trim() || "Onboarding";
164
+ if (data.meta?.status !== "filled") {
165
+ return renderPendingPage(data, options.root, lang, t);
166
+ }
167
+ const theme = themeFor((0, brand_1.resolveBrand)(data, options.root));
168
+ // 1. capítulos do meio, numerados só quando têm conteúdo
169
+ const middle = [];
170
+ let num = 1;
171
+ for (const [id, build] of slides_1.CHAPTERS) {
172
+ const slides = build({ data, t, num });
173
+ if (slides.length) {
174
+ middle.push({ id, slides });
175
+ num++;
176
+ }
177
+ }
178
+ // 2. índice de capítulos com a posição de cada um no deck
179
+ const chapters = [];
180
+ let cursor = 1; // slide 0 é a boas-vindas
181
+ chapters.push({ id: "welcome", num: 0, name: t.chapters.welcome, start: 0, count: 1 });
182
+ middle.forEach((c, i) => {
183
+ chapters.push({ id: c.id, num: i + 1, name: t.chapters[c.id], start: cursor, count: c.slides.length });
184
+ cursor += c.slides.length;
185
+ });
186
+ chapters.push({ id: "finale", num: middle.length + 1, name: t.chapters.finale, start: cursor, count: 1 });
187
+ const total = cursor + 1;
188
+ const minutes = Math.max(3, Math.round(total * 0.4));
189
+ const hasTasks = middle.some((c) => c.id === "mission");
190
+ const slides = [welcomeSlide(data, t, chapters, minutes, theme), ...middle.flatMap((c) => c.slides), finaleSlide(t, chapters, hasTasks, theme)];
191
+ const chapterNum = (id) => (id === "welcome" ? "👋" : id === "finale" ? "🎉" : String(chapters.find((c) => c.id === id)?.num ?? ""));
192
+ const slideHtml = slides
193
+ .map((s, i) => `<section class="slide" data-kind="${(0, html_1.escapeHtml)(s.kind ?? s.chapter)}" data-chapter="${s.chapter}" data-chapter-name="${(0, html_1.escapeHtml)(t.chapters[s.chapter])}" data-chapter-num="${chapterNum(s.chapter)}" style="--h:${theme.hues[s.chapter]}" aria-roledescription="slide" aria-label="${i + 1} / ${total}" aria-hidden="true"><div class="slide-inner">${s.html}</div></section>`)
194
+ .join("\n");
195
+ const segments = chapters
196
+ .map((c) => `<button type="button" class="seg" data-go="${c.start}" data-start="${c.start}" data-count="${c.count}" style="--h:${theme.hues[c.id]};--w:${c.count}" title="${(0, html_1.escapeHtml)(c.name)}" aria-label="${(0, html_1.escapeHtml)(c.name)}"><span></span></button>`)
197
+ .join("");
198
+ let index = 0;
199
+ const overview = chapters
200
+ .map((c) => {
201
+ const items = slides
202
+ .slice(c.start, c.start + c.count)
203
+ .map((s) => `<button type="button" data-go="${index++}">${(0, html_1.escapeHtml)(s.title)}</button>`)
204
+ .join("");
205
+ return `<div class="ov-chapter" style="--ch:${theme.hues[c.id]}"><h3><span class="n">${chapterNum(c.id)}</span>${(0, html_1.escapeHtml)(c.name)}</h3>${items}</div>`;
206
+ })
207
+ .join("");
208
+ const body = `<div class="deck">
209
+ <header class="topbar">
210
+ <span class="brand">${logoImg(theme, "brand-mark")}<span class="brand-name">${(0, html_1.escapeHtml)(name)}</span></span>
211
+ <nav class="segments" aria-label="${(0, html_1.escapeHtml)(t.ui.chapters)}">${segments}</nav>
212
+ <button type="button" class="icon-btn" data-overview title="${(0, html_1.escapeHtml)(t.ui.overview)} (O)" aria-label="${(0, html_1.escapeHtml)(t.ui.overview)}">${ICON_GRID}</button>
213
+ <button type="button" class="icon-btn" data-theme-toggle title="${(0, html_1.escapeHtml)(t.ui.theme)}" aria-label="${(0, html_1.escapeHtml)(t.ui.theme)}">${ICON_THEME}</button>
214
+ </header>
215
+ <main class="stage" id="ok-stage">
216
+ ${slideHtml}
217
+ <div class="wipe" id="ok-wipe" aria-hidden="true"><span></span><strong></strong></div>
218
+ </main>
219
+ <footer class="controls">
220
+ <button type="button" class="nav-btn" data-prev aria-label="${(0, html_1.escapeHtml)(t.ui.prev)}">← <span class="txt">${(0, html_1.escapeHtml)(t.ui.prev)}</span></button>
221
+ <div class="where" aria-live="polite"><strong id="ok-chapter"></strong><span><span id="ok-count"></span><span class="hint" id="ok-hint"> · ${(0, html_1.escapeHtml)(t.ui.keys)}</span></span></div>
222
+ <button type="button" class="nav-btn primary" data-next aria-label="${(0, html_1.escapeHtml)(t.ui.next)}"><span class="txt">${(0, html_1.escapeHtml)(t.ui.next)}</span> →</button>
223
+ </footer>
224
+ </div>
225
+ <div class="overview" id="ok-overview" aria-hidden="true" role="dialog" aria-label="${(0, html_1.escapeHtml)(t.ui.overview)}">
226
+ <div class="overview-inner"><div class="overview-head"><h2>${(0, html_1.escapeHtml)(t.ui.overview)}</h2><button type="button" class="icon-btn" data-close aria-label="${(0, html_1.escapeHtml)(t.ui.close)}">✕</button></div><div class="ov-grid">${overview}</div></div>
227
+ </div>
228
+ <div class="toast" id="ok-toast" data-template="${(0, html_1.escapeHtml)(t.ui.resume)}" role="status"><span></span><button type="button" class="yes" data-resume="0">${(0, html_1.escapeHtml)(t.ui.resumeYes)}</button><button type="button" data-dismiss>${(0, html_1.escapeHtml)(t.ui.resumeNo)}</button></div>`;
229
+ return shell(lang, `${name} · Onboarding`, body, theme, { mermaid: slides.some((s) => s.kind === "diagram") && !(0, load_1.isBlank)(data.architecture?.diagram) });
230
+ }
@@ -0,0 +1,19 @@
1
+ import type { OnboardingData } from "../types";
2
+ import type { ChapterId, Copy } from "./copy";
3
+ export interface Slide {
4
+ chapter: ChapterId;
5
+ /** Título curto, usado no índice. */
6
+ title: string;
7
+ html: string;
8
+ kind?: string;
9
+ }
10
+ export interface Ctx {
11
+ data: OnboardingData;
12
+ t: Copy;
13
+ /** Número do capítulo (só capítulos com conteúdo contam). */
14
+ num: number;
15
+ }
16
+ type Builder = (ctx: Ctx) => Slide[];
17
+ /** Capítulos do meio, em ordem narrativa: do porquê ao código. */
18
+ export declare const CHAPTERS: [ChapterId, Builder][];
19
+ export {};
@@ -0,0 +1,232 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CHAPTERS = void 0;
4
+ const load_1 = require("../load");
5
+ const html_1 = require("./html");
6
+ function head(ctx, chapter, title, lede, page) {
7
+ const cont = page && page[1] > 1 ? ` <span class="cont">${page[0]}/${page[1]}</span>` : "";
8
+ return `<p class="kicker"><span class="num">${ctx.num}</span>${(0, html_1.escapeHtml)(ctx.t.chapters[chapter])}</p>
9
+ <h1 class="title">${(0, html_1.escapeHtml)(title)}${cont}</h1>${lede && (!page || page[0] === 1) ? `<p class="lede">${(0, html_1.text)(lede)}</p>` : ""}`;
10
+ }
11
+ /** Monta uma sequência de slides paginando uma lista. */
12
+ function paged(ctx, chapter, items, size, title, lede, body, kind) {
13
+ const pages = (0, html_1.chunk)(items, size);
14
+ return pages.map((page, i) => ({
15
+ chapter,
16
+ kind,
17
+ title: pages.length > 1 ? `${title} (${i + 1}/${pages.length})` : title,
18
+ html: `${head(ctx, chapter, title, lede, [i + 1, pages.length])}${body(page, i * size)}`,
19
+ }));
20
+ }
21
+ /* ───────────────────────── capítulos ───────────────────────── */
22
+ const why = (ctx) => {
23
+ const p = ctx.data.project;
24
+ if (!p || (0, load_1.isBlank)(p.purpose))
25
+ return [];
26
+ const { t } = ctx;
27
+ const cards = [
28
+ !(0, load_1.isBlank)(p.audience) ? `<div class="card"><span class="label">${t.why.audience}</span><p class="strong">${(0, html_1.text)(p.audience)}</p></div>` : "",
29
+ !(0, load_1.isBlank)(p.status) ? `<div class="card"><span class="label">${t.why.status}</span><p class="strong">${(0, html_1.text)(p.status)}</p></div>` : "",
30
+ ].join("");
31
+ return [
32
+ {
33
+ chapter: "why",
34
+ title: t.why.title,
35
+ html: `${head(ctx, "why", t.why.title, t.why.lede)}<p class="statement">${(0, html_1.text)(p.purpose)}</p>${cards ? `<div class="grid-2 reveal">${cards}</div>` : ""}`,
36
+ },
37
+ ];
38
+ };
39
+ const business = (ctx) => {
40
+ const b = ctx.data.business;
41
+ if (!b)
42
+ return [];
43
+ const { t, data } = ctx;
44
+ const slides = [];
45
+ if (!(0, load_1.isBlank)(b.model) || !(0, load_1.isBlank)(b.valueProposition)) {
46
+ const cards = [
47
+ !(0, load_1.isBlank)(b.valueProposition)
48
+ ? `<div class="card big-card"><span class="emoji" aria-hidden="true">💎</span><span class="label">${t.business.value}</span><p>${(0, html_1.text)(b.valueProposition)}</p></div>`
49
+ : "",
50
+ !(0, load_1.isBlank)(b.model)
51
+ ? `<div class="card big-card"><span class="emoji" aria-hidden="true">🧭</span><span class="label">${t.business.model}</span><p>${(0, html_1.text)(b.model)}</p></div>`
52
+ : "",
53
+ ].join("");
54
+ slides.push({ chapter: "business", title: t.business.title, html: `${head(ctx, "business", t.business.title, t.business.lede)}<div class="grid-2 reveal">${cards}</div>` });
55
+ }
56
+ const actors = (0, load_1.filled)(b.actors);
57
+ slides.push(...paged(ctx, "business", actors, 6, t.business.actorsTitle, t.business.actorsLede, (page) => `<div class="grid reveal">${page
58
+ .map((a) => `<div class="card actor"><span class="avatar" style="--ah:${(0, html_1.hueFrom)(a.name)}">${(0, html_1.escapeHtml)((0, html_1.initials)(a.name))}</span><div><h3>${(0, html_1.escapeHtml)(a.name)}</h3><p>${(0, html_1.text)(a.description)}</p>${(0, html_1.evidenceList)(a.evidence, data)}</div></div>`)
59
+ .join("")}</div>`));
60
+ const flows = (0, load_1.filled)(b.flows);
61
+ slides.push(...paged(ctx, "business", flows, 2, t.business.flowsTitle, t.business.flowsLede, (page) => page
62
+ .map((f) => `<div class="flow"><h3>${(0, html_1.escapeHtml)(f.name)}</h3><ol class="chain reveal">${(0, load_1.filled)(f.steps)
63
+ .map((s, i) => `<li><span class="step"><b>${i + 1}</b>${(0, html_1.text)(s)}</span></li>`)
64
+ .join("")}</ol>${(0, html_1.evidenceList)(f.evidence, data)}</div>`)
65
+ .join("")));
66
+ return slides;
67
+ };
68
+ const product = (ctx) => {
69
+ const { t, data } = ctx;
70
+ return paged(ctx, "product", (0, load_1.filled)(data.features), 6, t.product.title, t.product.lede, (page) => `<div class="grid reveal">${page
71
+ .map((f) => `<div class="card feature"><div class="card-top"><h3>${(0, html_1.escapeHtml)(f.name)}</h3>${f.status ? `<span class="badge">${(0, html_1.escapeHtml)(f.status)}</span>` : ""}</div><p>${(0, html_1.text)(f.description)}</p>${(0, html_1.evidenceList)(f.entryPoints, data)}</div>`)
72
+ .join("")}</div>`);
73
+ };
74
+ const language = (ctx) => {
75
+ const { t, data } = ctx;
76
+ const terms = (0, load_1.filled)(data.domain?.glossary);
77
+ const slides = paged(ctx, "language", terms, 8, t.language.title, t.language.lede, (page) => `<div class="flips reveal">${page
78
+ .map((g) => `<button class="flip" type="button" aria-pressed="false"><span class="flip-inner"><span class="face front"><strong>${(0, html_1.escapeHtml)(g.term)}</strong><small>${t.language.tap} ↻</small></span><span class="face back">${(0, html_1.text)(g.definition)}</span></span></button>`)
79
+ .join("")}</div>`);
80
+ if (terms.length >= 3) {
81
+ const picks = [...new Set([0, Math.floor(terms.length / 2), terms.length - 1])];
82
+ const questions = picks
83
+ .map((idx, qi) => {
84
+ const right = terms[idx];
85
+ const wrong = [terms[(idx + 1) % terms.length], terms[(idx + 2) % terms.length]];
86
+ const options = [
87
+ { label: right.definition, correct: true },
88
+ ...wrong.map((w) => ({ label: w.definition, correct: false })),
89
+ ];
90
+ // embaralha de forma determinística, para a resposta certa não ficar sempre em primeiro
91
+ const shift = ((0, html_1.hueFrom)(right.term) + qi) % 3;
92
+ const rotated = options.slice(shift).concat(options.slice(0, shift));
93
+ return `<div class="quiz-q" data-quiz data-right="${(0, html_1.escapeHtml)(t.language.right)}" data-wrong="${(0, html_1.escapeHtml)(t.language.wrong)}"><p class="q">${(0, html_1.escapeHtml)(t.language.question(right.term))}</p><div class="opts">${rotated
94
+ .map((o) => `<button type="button" class="opt" data-correct="${o.correct ? 1 : 0}">${(0, html_1.text)(o.label)}</button>`)
95
+ .join("")}</div><p class="feedback" aria-live="polite"></p></div>`;
96
+ })
97
+ .join("");
98
+ slides.push({
99
+ chapter: "language",
100
+ kind: "quiz",
101
+ title: t.language.quizTitle,
102
+ html: `${head(ctx, "language", t.language.quizTitle, t.language.quizLede)}<div class="quiz reveal">${questions}</div><p class="score" data-quiz-score data-template="${(0, html_1.escapeHtml)(t.language.score)}" aria-live="polite"></p>`,
103
+ });
104
+ }
105
+ return slides;
106
+ };
107
+ const rules = (ctx) => {
108
+ const { t, data } = ctx;
109
+ return paged(ctx, "rules", (0, load_1.filled)(data.domain?.businessRules), 3, t.rules.title, t.rules.lede, (page, offset) => `<div class="rules reveal">${page
110
+ .map((r, i) => `<div class="card rule"><span class="rule-num" aria-label="${t.rules.rule} ${offset + i + 1}">${(0, html_1.pad)(offset + i + 1)}</span><div><h3>${(0, html_1.escapeHtml)(r.title)}</h3><p>${(0, html_1.text)(r.description)}</p>${(0, html_1.evidenceList)(r.evidence, data)}</div></div>`)
111
+ .join("")}</div>`);
112
+ };
113
+ const stack = (ctx) => {
114
+ const { t, data } = ctx;
115
+ const items = (0, load_1.filled)(data.stack).sort((a, b) => (a.category || "").localeCompare(b.category || ""));
116
+ return paged(ctx, "stack", items, 6, t.stack.title, t.stack.lede, (page) => `<div class="grid reveal">${page
117
+ .map((s) => `<div class="card tech"><div class="tech-head"><span class="logo" style="--th:${(0, html_1.hueFrom)(s.name)}">${(0, html_1.escapeHtml)((0, html_1.initials)(s.name))}</span><div><strong>${(0, html_1.escapeHtml)(s.name)}</strong><small>${(0, html_1.escapeHtml)([s.category, s.version].filter(Boolean).join(" · "))}</small></div></div><p>${(0, html_1.text)(s.why)}</p>${(0, html_1.evidenceList)(s.evidence, data)}</div>`)
118
+ .join("")}</div>`);
119
+ };
120
+ const architecture = (ctx) => {
121
+ const { t, data } = ctx;
122
+ const a = data.architecture;
123
+ if (!a)
124
+ return [];
125
+ const slides = [];
126
+ if (!(0, load_1.isBlank)(a.summary) || !(0, load_1.isBlank)(a.diagram)) {
127
+ const hasDiagram = !(0, load_1.isBlank)(a.diagram);
128
+ slides.push({
129
+ chapter: "architecture",
130
+ kind: hasDiagram ? "diagram" : undefined,
131
+ title: t.architecture.title,
132
+ html: `${head(ctx, "architecture", t.architecture.title, hasDiagram ? a.summary : undefined)}${hasDiagram ? `<div class="diagram reveal"><pre class="mermaid">${(0, html_1.escapeHtml)(a.diagram)}</pre></div>` : `<p class="statement">${(0, html_1.text)(a.summary)}</p>`}`,
133
+ });
134
+ }
135
+ slides.push(...paged(ctx, "architecture", (0, load_1.filled)(a.modules), 6, t.architecture.modulesTitle, t.architecture.modulesLede, (page) => `<div class="modules reveal">${page
136
+ .map((m) => `<div class="module"><div><strong>${(0, html_1.escapeHtml)(m.name)}</strong><code>${(0, html_1.escapeHtml)(m.path)}</code></div><p>${(0, html_1.text)(m.responsibility)}</p></div>`)
137
+ .join("")}</div>`));
138
+ return slides;
139
+ };
140
+ const setup = (ctx) => {
141
+ const { t, data } = ctx;
142
+ const gs = data.gettingStarted;
143
+ if (!gs)
144
+ return [];
145
+ const slides = [];
146
+ const prereq = (0, load_1.filled)(gs.prerequisites);
147
+ const steps = (0, load_1.filled)(gs.setup);
148
+ if (prereq.length || steps.length) {
149
+ slides.push({
150
+ chapter: "setup",
151
+ title: t.setup.title,
152
+ html: `${head(ctx, "setup", t.setup.title, t.setup.lede)}${prereq.length ? `<p class="mini-label">${t.setup.prereq}</p><ul class="pills reveal">${prereq.map((p) => `<li>${(0, html_1.text)(p)}</li>`).join("")}</ul>` : ""}${steps.length ? `<ol class="steps reveal">${steps.map((s) => `<li><span>${(0, html_1.text)(s)}</span></li>`).join("")}</ol>` : ""}`,
153
+ });
154
+ }
155
+ slides.push(...paged(ctx, "setup", (0, load_1.filled)(gs.commands), 6, t.setup.commandsTitle, t.setup.commandsLede, (page) => `<div class="terminal reveal"><div class="term-bar" aria-hidden="true"><i></i><i></i><i></i></div>${page
156
+ .map((c) => `<div class="cmd"><div><code>${(0, html_1.escapeHtml)(c.command)}</code>${c.description ? `<p>${(0, html_1.text)(c.description)}</p>` : ""}</div><button type="button" class="copy" data-copy="${(0, html_1.escapeHtml)(c.command)}" data-done="${(0, html_1.escapeHtml)(t.setup.copied)}">${t.setup.copy}</button></div>`)
157
+ .join("")}</div>`));
158
+ slides.push(...paged(ctx, "setup", (0, load_1.filled)(gs.envVars), 8, t.setup.envTitle, t.setup.envLede, (page) => `<div class="grid reveal">${page
159
+ .map((v) => `<div class="card env"><div class="card-top"><code>${(0, html_1.escapeHtml)(v.name)}</code><span class="badge ${v.required ? "badge-strong" : ""}">${v.required ? t.setup.required : t.setup.optional}</span></div><p>${(0, html_1.text)(v.description)}</p></div>`)
160
+ .join("")}</div>`));
161
+ return slides;
162
+ };
163
+ const conventions = (ctx) => {
164
+ const { t, data } = ctx;
165
+ return paged(ctx, "conventions", (0, load_1.filled)(data.conventions), 2, t.conventions.title, t.conventions.lede, (page) => `<div class="grid-2 reveal">${page
166
+ .map((c) => `<div class="card convention"><h3>${(0, html_1.escapeHtml)(c.title)}</h3><p>${(0, html_1.text)(c.description)}</p>${c.example?.trim() ? `<pre><code>${(0, html_1.escapeHtml)(c.example)}</code></pre>` : ""}${(0, html_1.evidenceList)(c.evidence, data)}</div>`)
167
+ .join("")}</div>`);
168
+ };
169
+ const tours = (ctx) => {
170
+ const { t, data } = ctx;
171
+ const slides = [];
172
+ for (const tour of (0, load_1.filled)(data.tours)) {
173
+ const steps = (0, load_1.filled)(tour.steps);
174
+ if (!steps.length)
175
+ continue;
176
+ slides.push({
177
+ chapter: "tours",
178
+ title: tour.title,
179
+ html: `<p class="kicker"><span class="num">${ctx.num}</span>${(0, html_1.escapeHtml)(t.tours.kicker)}</p><h1 class="title">${(0, html_1.escapeHtml)(tour.title)}</h1><p class="lede">${(0, html_1.text)(tour.description || t.tours.intro)}</p><p class="mini-label">${t.tours.stops}</p><ol class="route reveal">${steps
180
+ .map((s) => `<li><code>${(0, html_1.escapeHtml)(s.file)}</code></li>`)
181
+ .join("")}</ol>`,
182
+ });
183
+ steps.forEach((s, i) => {
184
+ slides.push({
185
+ chapter: "tours",
186
+ kind: "tour-step",
187
+ title: `${tour.title} · ${i + 1}`,
188
+ html: `<p class="kicker"><span class="num">${ctx.num}</span>${(0, html_1.escapeHtml)(tour.title)}</p><div class="tour-step"><span class="bignum" aria-hidden="true">${(0, html_1.pad)(i + 1)}</span><div><p class="mini-label">${(0, html_1.escapeHtml)(t.tours.stop(i + 1, steps.length))}</p>${(0, html_1.evidenceChip)(s.file, data, "ev-lg")}<p class="statement small">${(0, html_1.text)(s.explanation)}</p><div class="stops" aria-hidden="true">${steps
189
+ .map((_, j) => `<i class="${j <= i ? "on" : ""}"></i>`)
190
+ .join("")}</div></div></div>`,
191
+ });
192
+ });
193
+ }
194
+ return slides;
195
+ };
196
+ const gotchas = (ctx) => {
197
+ const { t, data } = ctx;
198
+ return paged(ctx, "gotchas", (0, load_1.filled)(data.gotchas), 3, t.gotchas.title, t.gotchas.lede, (page) => `<div class="stack-list reveal">${page
199
+ .map((g) => `<div class="card gotcha"><span class="emoji" aria-hidden="true">🍌</span><div><h3>${(0, html_1.escapeHtml)(g.title)}</h3><p>${(0, html_1.text)(g.description)}</p>${(0, html_1.evidenceList)(g.evidence, data)}</div></div>`)
200
+ .join("")}</div>`);
201
+ };
202
+ const decisions = (ctx) => {
203
+ const { t, data } = ctx;
204
+ return paged(ctx, "decisions", (0, load_1.filled)(data.decisions), 2, t.decisions.title, t.decisions.lede, (page) => `<div class="stack-list reveal">${page
205
+ .map((d) => `<div class="card decision"><h3>${(0, html_1.escapeHtml)(d.title)}</h3><div class="split">${d.context ? `<div><span class="label">${t.decisions.context}</span><p>${(0, html_1.text)(d.context)}</p></div>` : ""}${d.decision ? `<div><span class="label">${t.decisions.decision}</span><p>${(0, html_1.text)(d.decision)}</p></div>` : ""}</div>${(0, html_1.evidenceList)(d.evidence, data)}</div>`)
206
+ .join("")}</div>`);
207
+ };
208
+ const mission = (ctx) => {
209
+ const { t, data } = ctx;
210
+ return paged(ctx, "mission", (0, load_1.filled)(data.firstTasks), 5, t.mission.title, t.mission.lede, (page) => `<div class="tasks reveal">${page
211
+ .map((task) => {
212
+ const id = `task-${(0, html_1.hueFrom)(task.title)}-${task.title.length}`;
213
+ return `<label class="task"><input type="checkbox" data-task="${id}"><span><strong>${(0, html_1.escapeHtml)(task.title)}</strong><span class="task-desc">${(0, html_1.text)(task.description)}</span>${(0, html_1.evidenceList)(task.files, data)}</span></label>`;
214
+ })
215
+ .join("")}</div>`);
216
+ };
217
+ /** Capítulos do meio, em ordem narrativa: do porquê ao código. */
218
+ exports.CHAPTERS = [
219
+ ["why", why],
220
+ ["business", business],
221
+ ["product", product],
222
+ ["language", language],
223
+ ["rules", rules],
224
+ ["stack", stack],
225
+ ["architecture", architecture],
226
+ ["setup", setup],
227
+ ["conventions", conventions],
228
+ ["tours", tours],
229
+ ["gotchas", gotchas],
230
+ ["decisions", decisions],
231
+ ["mission", mission],
232
+ ];
@@ -0,0 +1,14 @@
1
+ import type { Framework, OnboardingData } from "./types";
2
+ export declare const SCHEMA_VERSION = "1";
3
+ export interface TemplateOptions {
4
+ framework: Framework;
5
+ language?: string;
6
+ }
7
+ export declare function createTemplate({ framework, language }: TemplateOptions): OnboardingData;
8
+ /**
9
+ * Frase para colar na IA local. Com `init`, pede para a IA criar o arquivo antes
10
+ * (usado quando o onboarding.json ainda não existe).
11
+ */
12
+ export declare function aiPrompt(file?: string, options?: {
13
+ init?: boolean;
14
+ }): string;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SCHEMA_VERSION = void 0;
4
+ exports.createTemplate = createTemplate;
5
+ exports.aiPrompt = aiPrompt;
6
+ exports.SCHEMA_VERSION = "1";
7
+ const FRAMEWORK_HINTS = {
8
+ next: "Projeto Next.js. Identifique se usa App Router (app/) ou Pages Router (pages/), as rotas e layouts, Server Components e Client Components ('use client'), Server Actions, route handlers (app/**/route.ts), middleware.ts, estratégia de fetch e cache, autenticação, estilização (Tailwind, CSS Modules etc.) e onde fica o acesso a dados. Use as rotas principais como base para features e tours. Ignore a rota app/onboarding criada pelo onboarding-kit.",
9
+ nest: "Projeto NestJS. Mapeie os módulos (*.module.ts) e as dependências entre eles para architecture.modules e o diagrama. Levante controllers e rotas, providers e services, guards, interceptors, pipes, DTOs e validação (class-validator), entidades e ORM (TypeORM, Prisma, Mongoose), filas, jobs agendados e configuração (ConfigModule). As regras de negócio costumam estar nos services. Ignore o OnboardingModule do onboarding-kit.",
10
+ node: "Projeto Node.js. Identifique o ponto de entrada (main e scripts do package.json), o framework HTTP (Express, Fastify, Koa ou http nativo), a organização de rotas e middlewares, o acesso a dados, jobs/workers e scripts utilitários.",
11
+ };
12
+ const SECTION_INSTRUCTIONS = {
13
+ project: "name: nome do projeto. tagline: uma frase que resume o projeto. purpose: 2 a 4 frases sobre o problema que ele resolve e para quem. audience: quem usa o produto (usuários finais, não devs). status: fase do projeto (ex.: em produção, MVP, em migração). repoUrl: URL do repositório (git remote get-url origin, convertida para https), vazio se não houver.",
14
+ business: "Explique o negócio para alguém que nunca ouviu falar do produto. model: como o produto gera valor ou receita (assinatura, comissão, venda, uso interno...). Baseie-se em planos, pagamentos, roles e docs do repositório; se não der para inferir a receita, descreva o valor gerado sem inventar. valueProposition: o que o produto resolve para quem usa, em 1 ou 2 frases. actors: os perfis que participam (tipos de usuário, roles, sistemas parceiros), com evidence de onde aparecem no código. flows: 1 a 3 jornadas principais (ex.: 'Do cadastro à primeira compra'), cada uma com 3 a 6 passos curtos e evidence.",
15
+ gettingStarted: "prerequisites: o que precisa estar instalado (versão do Node, banco, Docker...). setup: passos em ordem para rodar localmente pela primeira vez. commands: scripts úteis do package.json com o comando exato e o que fazem. envVars: variáveis de ambiente (de .env.example, código ou docs) com nome, para que servem e se são obrigatórias. NUNCA inclua valores.",
16
+ stack: "Uma entrada por tecnologia relevante (linguagem, framework, banco, ORM, auth, testes, lint, deploy, filas, libs centrais). category: ex. framework, database, orm, testing, styling, infra. version: do package.json/lockfile. why: para que é usada NESTE projeto. evidence: onde aparece (package.json, arquivo de config ou de uso).",
17
+ architecture: "summary: como o código está organizado e como uma requisição/ação flui pelo sistema. diagram: diagrama Mermaid (graph TD ou flowchart) com os principais módulos e integrações externas, sem cercas ```. modules: pastas ou módulos principais com path e responsibility.",
18
+ conventions: "Padrões de código que alguém novo precisa seguir: nomenclatura, estrutura de pastas, tratamento de erro, validação, padrões de componentes, testes, commits, lint/format. example: trecho curto real ou ilustrativo. evidence obrigatória.",
19
+ domain: "glossary: termos do negócio que aparecem no código e nas telas, com definição em linguagem simples. businessRules: regras de negócio macro (ex.: quem pode fazer o quê, limites, cálculos, estados de um pedido), com evidence apontando onde a regra está implementada.",
20
+ features: "Funcionalidades do produto do ponto de vista do usuário. entryPoints: arquivos onde a feature começa (página, controller, rota). status: ex. estável, beta, em desenvolvimento, descontinuada.",
21
+ tours: "1 a 3 tours guiados pelo código, ex.: 'Como uma requisição é atendida', 'Ciclo de vida de um pedido'. Cada step aponta um arquivo (com linha quando ajudar) e explica o que olhar ali. Ordene os steps na sequência de leitura.",
22
+ gotchas: "Armadilhas e cuidados: comportamentos não óbvios, partes frágeis, workarounds, TODOs/FIXMEs importantes, coisas que costumam quebrar. Só inclua o que tiver evidence.",
23
+ decisions: "Decisões de arquitetura perceptíveis no código ou na documentação (ex.: por que tal ORM, monorepo, cache, autenticação). context: o problema. decision: o que foi escolhido. Não invente motivos: se o motivo não está documentado, descreva só a decisão.",
24
+ brand: "Identidade visual, usada para a apresentação ter a cara da plataforma. primaryColor: a cor principal da marca em hex (#RRGGBB). Procure em tailwind.config (theme.extend.colors: brand, primary), no @theme do Tailwind v4 (--color-primary, --color-brand), em variáveis CSS de globals.css/index.css (--primary, --brand; converta HSL/OKLCH para hex), em temas de MUI/Chakra/styled-components (palette.primary.main) e no theme_color do manifest. palette: 1 a 4 outras cores da marca em hex (secundária, destaque). Ignore branco, preto e cinzas quando houver uma cor de verdade. logo: caminho do logo do projeto (procure em public/, assets/, src/assets/, app/icon.*); prefira SVG e a versão para fundo claro; ignore os arquivos de exemplo do framework (next.svg, vercel.svg, vite.svg, react.svg). favicon: caminho do favicon (public/favicon.ico, app/favicon.ico, app/icon.png). evidence: arquivos de onde tirou as cores. Deixe vazio o que não encontrar: o onboarding-kit tenta detectar sozinho.",
25
+ firstTasks: "3 a 5 tarefas pequenas e seguras para alguém novo começar (ex.: um teste faltando, um TODO simples, uma melhoria de mensagem de erro), com os arquivos envolvidos.",
26
+ };
27
+ function createTemplate({ framework, language = "pt-BR" }) {
28
+ return {
29
+ _instructions: {
30
+ readme: "Você é a IA local deste projeto. Sua tarefa é preencher este arquivo analisando o código do repositório. O resultado será exibido em /onboarding para quem está chegando no projeto.",
31
+ steps: [
32
+ "1. Leia este arquivo inteiro antes de começar, incluindo _instructions.sections.",
33
+ "2. Explore o repositório: package.json e lockfile, README e docs, arquivos de config (tsconfig, eslint, prettier, next.config, nest-cli.json, docker, CI), código-fonte, testes e .env.example.",
34
+ "3. Preencha cada seção seguindo _instructions.sections.<seção> e _instructions.framework. Os arrays vêm com um item modelo em branco: substitua-o por itens reais, quantos forem necessários. Se não houver nada real para a seção, deixe o array vazio ([]).",
35
+ "4. Preencha meta: status = \"filled\", generatedAt = data e hora atuais em ISO 8601, generatedBy = nome da ferramenta e do modelo, commit = saída de `git rev-parse HEAD` (vazio se não houver git).",
36
+ "5. Rode `npx onboarding-kit validate` e corrija tudo o que ele apontar até passar sem erros.",
37
+ ],
38
+ rules: [
39
+ "Escreva todo o conteúdo no idioma de meta.language.",
40
+ "Nunca invente. Toda afirmação sobre stack, convenções, regras, decisões e armadilhas precisa de evidence com arquivos reais, no formato caminho/relativo ou caminho/relativo:linha. Se não encontrar evidência, não inclua o item.",
41
+ "Caminhos são relativos à raiz do repositório e usam '/'.",
42
+ "Nunca copie segredos: valores de .env, tokens, chaves de API, senhas ou URLs com credenciais. Em envVars descreva apenas o nome e a finalidade.",
43
+ "Mantenha o JSON válido. Não renomeie nem remova chaves e não apague _instructions: ele é usado nas próximas atualizações.",
44
+ "Escreva de forma curta, direta e humana, como um colega explicando o projeto: quem lê é uma pessoa no primeiro dia. O conteúdo vira slides, então prefira frases curtas a parágrafos longos.",
45
+ "Se meta.status já for \"filled\", você está ATUALIZANDO: use `git diff <meta.commit>..HEAD` para revisar apenas o que mudou, mantenha o restante e atualize meta.commit e meta.generatedAt.",
46
+ ],
47
+ framework: FRAMEWORK_HINTS[framework],
48
+ sections: SECTION_INSTRUCTIONS,
49
+ },
50
+ meta: {
51
+ schemaVersion: exports.SCHEMA_VERSION,
52
+ status: "pending",
53
+ language,
54
+ framework,
55
+ generatedAt: "",
56
+ generatedBy: "",
57
+ commit: "",
58
+ },
59
+ project: { name: "", tagline: "", purpose: "", audience: "", status: "", repoUrl: "" },
60
+ business: {
61
+ model: "",
62
+ valueProposition: "",
63
+ actors: [{ name: "", description: "", evidence: [""] }],
64
+ flows: [{ name: "", steps: [""], evidence: [""] }],
65
+ },
66
+ gettingStarted: {
67
+ prerequisites: [""],
68
+ setup: [""],
69
+ commands: [{ name: "", command: "", description: "" }],
70
+ envVars: [{ name: "", description: "", required: false }],
71
+ },
72
+ stack: [{ name: "", category: "", version: "", why: "", evidence: [""] }],
73
+ architecture: { summary: "", diagram: "", modules: [{ name: "", path: "", responsibility: "" }] },
74
+ conventions: [{ title: "", description: "", example: "", evidence: [""] }],
75
+ domain: {
76
+ glossary: [{ term: "", definition: "" }],
77
+ businessRules: [{ title: "", description: "", evidence: [""] }],
78
+ },
79
+ features: [{ name: "", description: "", entryPoints: [""], status: "" }],
80
+ tours: [{ title: "", description: "", steps: [{ file: "", explanation: "" }] }],
81
+ gotchas: [{ title: "", description: "", evidence: [""] }],
82
+ decisions: [{ title: "", context: "", decision: "", evidence: [""] }],
83
+ firstTasks: [{ title: "", description: "", files: [""] }],
84
+ brand: { primaryColor: "", palette: [""], logo: "", favicon: "", evidence: [""] },
85
+ };
86
+ }
87
+ /**
88
+ * Frase para colar na IA local. Com `init`, pede para a IA criar o arquivo antes
89
+ * (usado quando o onboarding.json ainda não existe).
90
+ */
91
+ function aiPrompt(file = "onboarding.json", options = {}) {
92
+ if (options.init) {
93
+ return `Rode "npx onboarding-kit init" na raiz deste projeto para criar o ${file}. Depois leia o arquivo e siga as instruções em "_instructions" para preenchê-lo com base neste repositório. No final, rode "npx onboarding-kit validate" e corrija o que ele apontar.`;
94
+ }
95
+ return `Leia o arquivo ${file} e siga as instruções em "_instructions" para preenchê-lo com base neste repositório. No final, rode "npx onboarding-kit validate" e corrija o que ele apontar.`;
96
+ }