vireum-spec-cli 0.5.0 → 0.7.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,165 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runInitWeb = runInitWeb;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const prompts_1 = require("@inquirer/prompts");
9
+ const system_1 = require("./system");
10
+ async function runInitWeb() {
11
+ console.log(chalk_1.default.hex('#2D7DD2').bold('\n Vireum Spec — Briefing Interativo (Web)\n'));
12
+ console.log(chalk_1.default.gray(' Responda as perguntas abaixo. As respostas serão salvas em .spec/briefing.md\n'));
13
+ // ── 1. Informações Gerais ──────────────────────────────────────────────────
14
+ console.log(chalk_1.default.hex('#2D7DD2').bold('📌 1. Informações Gerais\n'));
15
+ const geral = {
16
+ projeto: await (0, prompts_1.input)({ message: 'Nome do projeto:' }),
17
+ cliente: await (0, prompts_1.input)({ message: 'Nome do cliente:' }),
18
+ responsavel: await (0, prompts_1.input)({ message: 'Responsável pelo projeto (Vireum):' }),
19
+ data: await (0, prompts_1.input)({ message: 'Data da reunião:', default: new Date().toLocaleDateString('pt-BR') }),
20
+ };
21
+ // ── 2. Objetivo ────────────────────────────────────────────────────────────
22
+ console.log(chalk_1.default.hex('#2D7DD2').bold('\n🎯 2. Objetivo e Público\n'));
23
+ const objetivo = {
24
+ objetivo: await (0, prompts_1.input)({ message: 'Qual é o objetivo principal do site?' }),
25
+ cta_principal: await (0, prompts_1.input)({ message: 'Qual é o CTA principal? (ex: agendar consulta, comprar, cadastrar)' }),
26
+ publico: await (0, prompts_1.input)({ message: 'Público-alvo do site:' }),
27
+ subtipo: await (0, prompts_1.select)({
28
+ message: 'Tipo de site:',
29
+ choices: [
30
+ { name: 'Landing Page — foco em conversão, CTA único', value: 'landing' },
31
+ { name: 'Institucional — múltiplas páginas, apresentação da empresa', value: 'institucional' },
32
+ { name: 'Blog / Conteúdo — foco em artigos e SEO', value: 'blog' },
33
+ { name: 'Portfólio — showcase de trabalhos', value: 'portfolio' },
34
+ { name: 'One Page — scroll contínuo, evento ou produto', value: 'onepage' },
35
+ ],
36
+ }),
37
+ prazo_mvp: await (0, prompts_1.input)({ message: 'Prazo esperado para entrega:' }),
38
+ };
39
+ // ── 3. Conteúdo ────────────────────────────────────────────────────────────
40
+ console.log(chalk_1.default.hex('#2D7DD2').bold('\n🧩 3. Conteúdo e Seções\n'));
41
+ const conteudo = {
42
+ secoes: await (0, prompts_1.input)({ message: 'Seções da página (separadas por vírgula, ex: hero, sobre, serviços, depoimentos, contato):' }),
43
+ formulario: await (0, prompts_1.confirm)({ message: 'Tem formulário de contato ou captura de leads?', default: false }),
44
+ campos_form: objetivo.subtipo !== 'blog'
45
+ ? await (0, prompts_1.input)({ message: 'Campos do formulário (separados por vírgula):' })
46
+ : '',
47
+ blog_categorias: objetivo.subtipo === 'blog'
48
+ ? await (0, prompts_1.input)({ message: 'Categorias do blog:' })
49
+ : '',
50
+ cms: await (0, prompts_1.confirm)({ message: 'O cliente vai gerenciar o conteúdo sozinho (precisa de CMS)?', default: false }),
51
+ };
52
+ // ── 4. Integrações ─────────────────────────────────────────────────────────
53
+ console.log(chalk_1.default.hex('#2D7DD2').bold('\n🔗 4. Integrações\n'));
54
+ const integracoes = {
55
+ analytics: await (0, prompts_1.confirm)({ message: 'Precisa de analytics (Google Analytics, Hotjar)?', default: false }),
56
+ chat: await (0, prompts_1.confirm)({ message: 'Precisa de chat ao vivo (Intercom, Tawk, etc)?', default: false }),
57
+ crm: await (0, prompts_1.input)({ message: 'Integração com CRM ou e-mail marketing? (HubSpot, RD Station, Mailchimp — deixe em branco se não):' }),
58
+ pixel: await (0, prompts_1.confirm)({ message: 'Precisa de pixel de rastreamento (Meta, Google Ads)?', default: false }),
59
+ };
60
+ // ── 5. Design ──────────────────────────────────────────────────────────────
61
+ console.log(chalk_1.default.hex('#2D7DD2').bold('\n🎨 5. Design e Identidade Visual\n'));
62
+ const design = {
63
+ tem_identidade: await (0, prompts_1.confirm)({ message: 'Já existe identidade visual (logo, cores, tipografia)?', default: false }),
64
+ referencia: await (0, prompts_1.input)({ message: 'Site de referência ou inspiração (URL ou descrição):' }),
65
+ design_system: await (0, prompts_1.select)({
66
+ message: 'Framework de UI:',
67
+ choices: [
68
+ { name: 'Tailwind CSS', value: 'Tailwind CSS' },
69
+ { name: 'Shadcn/ui', value: 'Shadcn/ui' },
70
+ { name: 'Bootstrap', value: 'Bootstrap' },
71
+ { name: 'CSS puro', value: 'CSS puro' },
72
+ { name: 'A definir', value: 'A definir' },
73
+ ],
74
+ }),
75
+ responsivo: await (0, prompts_1.confirm)({ message: 'Precisa ser responsivo (mobile-first)?', default: true }),
76
+ };
77
+ // ── 6. Deploy e SEO ────────────────────────────────────────────────────────
78
+ console.log(chalk_1.default.hex('#2D7DD2').bold('\n🚀 6. Deploy e SEO\n'));
79
+ const deploy = {
80
+ plataforma: await (0, prompts_1.select)({
81
+ message: 'Plataforma de deploy:',
82
+ choices: [
83
+ { name: 'Vercel', value: 'Vercel' },
84
+ { name: 'Netlify', value: 'Netlify' },
85
+ { name: 'GitHub Pages', value: 'GitHub Pages' },
86
+ { name: 'VPS / servidor próprio', value: 'VPS' },
87
+ { name: 'A definir', value: 'A definir' },
88
+ ],
89
+ }),
90
+ custo_operacional: await (0, prompts_1.select)({
91
+ message: 'Custo operacional mensal estimado:',
92
+ choices: [
93
+ { name: 'Até R$ 200 — projeto leve, baixo tráfego', value: 'Até R$ 200' },
94
+ { name: 'R$ 200–800 — projeto moderado', value: 'R$ 200–800' },
95
+ { name: 'R$ 800–3k — projeto robusto', value: 'R$ 800–3k' },
96
+ { name: 'Acima de R$ 3k — missão crítica / alta escala', value: 'Acima de R$ 3k' },
97
+ { name: 'A definir com o cliente', value: 'A definir' },
98
+ ],
99
+ }),
100
+ dominio: await (0, prompts_1.confirm)({ message: 'Já tem domínio configurado?', default: false }),
101
+ seo: await (0, prompts_1.confirm)({ message: 'SEO é requisito (meta tags, sitemap, Open Graph)?', default: true }),
102
+ performance: await (0, prompts_1.confirm)({ message: 'Performance é crítica (Core Web Vitals)?', default: false }),
103
+ };
104
+ const briefing = gerarBriefingWeb({ geral, objetivo, conteudo, integracoes, design, deploy });
105
+ (0, system_1.salvarBriefing)(briefing, 'web');
106
+ }
107
+ function gerarBriefingWeb(data) {
108
+ const { geral, objetivo, conteudo, integracoes, design, deploy } = data;
109
+ const secoes = conteudo.secoes
110
+ .split(',').map((s) => `- ${s.trim()}`).join('\n');
111
+ const subtipoLabel = {
112
+ landing: 'Landing Page',
113
+ institucional: 'Institucional',
114
+ blog: 'Blog / Conteúdo',
115
+ portfolio: 'Portfólio',
116
+ onepage: 'One Page',
117
+ };
118
+ return `# Briefing — ${geral.projeto}
119
+
120
+ ## 📌 1. Informações Gerais
121
+ - **Projeto:** ${geral.projeto}
122
+ - **Cliente:** ${geral.cliente}
123
+ - **Responsável:** ${geral.responsavel}
124
+ - **Data da reunião:** ${geral.data}
125
+ - **Tipo:** Web — ${subtipoLabel[objetivo.subtipo] || objetivo.subtipo}
126
+ - **Versão:** 1.0
127
+
128
+ ## 🎯 2. Objetivo e Público
129
+ - **Objetivo principal:** ${objetivo.objetivo}
130
+ - **CTA principal:** ${objetivo.cta_principal}
131
+ - **Público-alvo:** ${objetivo.publico}
132
+ - **Tipo de site:** ${subtipoLabel[objetivo.subtipo]}
133
+ - **Prazo de entrega:** ${objetivo.prazo_mvp}
134
+
135
+ ## 🧩 3. Conteúdo e Seções
136
+
137
+ ### Seções da página
138
+ ${secoes}
139
+
140
+ ### Formulário
141
+ - Formulário de contato/captura: ${conteudo.formulario ? 'Sim' : 'Não'}
142
+ ${conteudo.campos_form ? `- Campos: ${conteudo.campos_form}` : ''}
143
+ ${conteudo.blog_categorias ? `- Categorias do blog: ${conteudo.blog_categorias}` : ''}
144
+ - CMS para gestão de conteúdo: ${conteudo.cms ? 'Sim' : 'Não'}
145
+
146
+ ## 🔗 4. Integrações
147
+ - Analytics: ${integracoes.analytics ? 'Sim' : 'Não'}
148
+ - Chat ao vivo: ${integracoes.chat ? 'Sim' : 'Não'}
149
+ - CRM / E-mail marketing: ${integracoes.crm || 'Não'}
150
+ - Pixel de rastreamento: ${integracoes.pixel ? 'Sim' : 'Não'}
151
+
152
+ ## 🎨 5. Design e Identidade Visual
153
+ - Identidade visual existente: ${design.tem_identidade ? 'Sim' : 'Não'}
154
+ - Referência: ${design.referencia || 'Não informada'}
155
+ - Framework de UI: ${design.design_system}
156
+ - Responsivo: ${design.responsivo ? 'Sim' : 'Não'}
157
+
158
+ ## 🚀 6. Deploy e SEO
159
+ - Plataforma de deploy: ${deploy.plataforma}
160
+ - Custo operacional estimado: ${deploy.custo_operacional}
161
+ - Domínio configurado: ${deploy.dominio ? 'Sim' : 'Não'}
162
+ - SEO (meta tags, sitemap, OG): ${deploy.seo ? 'Sim' : 'Não'}
163
+ - Performance / Core Web Vitals: ${deploy.performance ? 'Crítico' : 'Normal'}
164
+ `;
165
+ }
@@ -1,261 +1,29 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
4
  };
38
5
  Object.defineProperty(exports, "__esModule", { value: true });
39
6
  exports.runInit = runInit;
40
7
  const chalk_1 = __importDefault(require("chalk"));
41
- const prompts_1 = require("@inquirer/prompts");
42
- const fs = __importStar(require("fs"));
43
- const path = __importStar(require("path"));
44
- async function runInit() {
45
- console.log(chalk_1.default.hex('#2D7DD2').bold('\n Vireum Spec Briefing Interativo\n'));
46
- console.log(chalk_1.default.gray(' Responda as perguntas abaixo. As respostas serão salvas em .spec/briefing.md\n'));
47
- // ── 1. Informações Gerais ──────────────────────────────────────────────────
48
- console.log(chalk_1.default.hex('#2D7DD2').bold('📌 1. Informações Gerais\n'));
49
- const geral = {
50
- projeto: await (0, prompts_1.input)({ message: 'Nome do projeto:' }),
51
- cliente: await (0, prompts_1.input)({ message: 'Nome do cliente:' }),
52
- responsavel: await (0, prompts_1.input)({ message: 'Responsável pelo projeto (Vireum):' }),
53
- data: await (0, prompts_1.input)({ message: 'Data da reunião:', default: new Date().toLocaleDateString('pt-BR') }),
54
- };
55
- // ── 2. Objetivo ────────────────────────────────────────────────────────────
56
- console.log(chalk_1.default.hex('#2D7DD2').bold('\n🎯 2. Objetivo do Projeto\n'));
57
- const objetivo = {
58
- objetivo: await (0, prompts_1.input)({ message: 'Qual é o objetivo principal da plataforma?' }),
59
- problema: await (0, prompts_1.input)({ message: 'Qual problema o sistema resolve?' }),
60
- curto_prazo: await (0, prompts_1.input)({ message: 'Resultado esperado no curto prazo:' }),
61
- medio_prazo: await (0, prompts_1.input)({ message: 'Resultado esperado no médio prazo:' }),
62
- longo_prazo: await (0, prompts_1.input)({ message: 'Resultado esperado no longo prazo:' }),
63
- prioridade_cliente: await (0, prompts_1.input)({ message: 'O que o cliente precisa que funcione primeiro?' }),
64
- };
65
- // ── 3. Escopo Funcional ────────────────────────────────────────────────────
66
- console.log(chalk_1.default.hex('#2D7DD2').bold('\n🧩 3. Escopo Funcional\n'));
67
- const escopo = {
68
- usuarios: await (0, prompts_1.input)({ message: 'Quem usará o sistema?' }),
69
- multi_usuario: await (0, prompts_1.confirm)({ message: 'Existem diferentes tipos de usuários?', default: false }),
70
- niveis_acesso: await (0, prompts_1.confirm)({ message: 'Existem níveis de acesso diferentes?', default: false }),
71
- features_obrigatorias: await (0, prompts_1.input)({ message: 'Funcionalidades obrigatórias (separadas por vírgula):' }),
72
- features_desejaveis: await (0, prompts_1.input)({ message: 'Funcionalidades desejáveis no MVP (separadas por vírgula):' }),
73
- integracoes: await (0, prompts_1.input)({ message: 'Integrações externas? (CRM, ERP, WhatsApp, etc.)' }),
74
- };
75
- // ── 4. Regras de Negócio ───────────────────────────────────────────────────
76
- console.log(chalk_1.default.hex('#2D7DD2').bold('\n🏗️ 4. Regras de Negócio\n'));
77
- const regras = {
78
- regras: await (0, prompts_1.input)({ message: 'Principais regras que o sistema deve obedecer:' }),
79
- excecoes: await (0, prompts_1.input)({ message: 'Exceções importantes:' }),
80
- };
81
- // ── 5. Financeiro ──────────────────────────────────────────────────────────
82
- console.log(chalk_1.default.hex('#2D7DD2').bold('\n💰 5. Modelo Financeiro\n'));
83
- const financeiro = {
84
- pagamento: await (0, prompts_1.confirm)({ message: 'Haverá integração com meio de pagamento?', default: false }),
85
- cobranca: await (0, prompts_1.select)({
86
- message: 'Modelo de cobrança:',
87
- choices: [
88
- { name: 'Não aplicável', value: 'Não aplicável' },
89
- { name: 'Assinatura recorrente', value: 'Assinatura recorrente' },
90
- { name: 'Compra única', value: 'Compra única' },
91
- { name: 'Ambos', value: 'Ambos' },
92
- ],
93
- }),
94
- estorno: await (0, prompts_1.confirm)({ message: 'Existem regras de estorno ou cancelamento?', default: false }),
95
- };
96
- // ── 6. Métricas ────────────────────────────────────────────────────────────
97
- console.log(chalk_1.default.hex('#2D7DD2').bold('\n📊 6. Métricas e Indicadores\n'));
98
- const metricas = {
99
- kpis: await (0, prompts_1.input)({ message: 'Quais KPIs devem ser acompanhados?' }),
100
- dashboard: await (0, prompts_1.confirm)({ message: 'Precisa de dashboard administrativo?', default: false }),
101
- relatorios: await (0, prompts_1.confirm)({ message: 'Precisa exportar relatórios?', default: false }),
102
- };
103
- // ── 7. Plataforma e DevOps ─────────────────────────────────────────────────
104
- console.log(chalk_1.default.hex('#2D7DD2').bold('\n📱 7. Plataforma e Infraestrutura\n'));
105
- const plataforma = {
106
- tipo: await (0, prompts_1.select)({
107
- message: 'O sistema será:',
108
- choices: [
109
- { name: 'Apenas Web', value: 'Apenas Web' },
110
- { name: 'Web + App', value: 'Web + App' },
111
- { name: 'Apenas App', value: 'Apenas App' },
112
- ],
113
- }),
114
- app_futuro: await (0, prompts_1.confirm)({ message: 'Pretende lançar aplicativo futuramente?', default: false }),
115
- push: await (0, prompts_1.confirm)({ message: 'Precisa de notificações push?', default: false }),
116
- hospedagem: await (0, prompts_1.input)({ message: 'Hospedagem preferida ou orçamento de infra:' }),
117
- cicd: await (0, prompts_1.confirm)({ message: 'Precisa de CI/CD desde o início?', default: false }),
118
- staging: await (0, prompts_1.confirm)({ message: 'Precisa de ambiente de staging?', default: false }),
119
- dominio: await (0, prompts_1.confirm)({ message: 'Já tem domínio/DNS configurado?', default: false }),
120
- backup_sla: await (0, prompts_1.confirm)({ message: 'Backups e SLA são requisito do cliente?', default: false }),
121
- };
122
- // ── 8. Segurança e Jurídico ────────────────────────────────────────────────
123
- console.log(chalk_1.default.hex('#2D7DD2').bold('\n🔐 8. Segurança e Jurídico\n'));
124
- const seguranca = {
125
- dados_sensiveis: await (0, prompts_1.confirm)({ message: 'Dados sensíveis serão armazenados?', default: false }),
126
- lgpd: await (0, prompts_1.confirm)({ message: 'Precisa de conformidade com LGPD?', default: true }),
127
- termos: await (0, prompts_1.confirm)({ message: 'Precisa de termos de uso?', default: false }),
128
- acesso_avancado: await (0, prompts_1.confirm)({ message: 'Haverá controle de acesso avançado?', default: false }),
129
- };
130
- // ── 9. Escala ──────────────────────────────────────────────────────────────
131
- console.log(chalk_1.default.hex('#2D7DD2').bold('\n📈 9. Escala e Crescimento\n'));
132
- const escala = {
133
- usuarios_lancamento: await (0, prompts_1.input)({ message: 'Número esperado de usuários no lançamento:' }),
134
- crescimento_1ano: await (0, prompts_1.input)({ message: 'Crescimento estimado em 1 ano:' }),
135
- uso: await (0, prompts_1.select)({
136
- message: 'Padrão de uso:',
137
- choices: [
138
- { name: 'Diário intenso', value: 'Diário intenso' },
139
- { name: 'Diário moderado', value: 'Diário moderado' },
140
- { name: 'Esporádico', value: 'Esporádico' },
141
- ],
142
- }),
143
- };
144
- // ── 10. Operacional ────────────────────────────────────────────────────────
145
- console.log(chalk_1.default.hex('#2D7DD2').bold('\n⚙️ 10. Operacional\n'));
146
- const operacional = {
147
- operador: await (0, prompts_1.input)({ message: 'Quem vai operar o sistema?' }),
148
- equipe: await (0, prompts_1.confirm)({ message: 'Haverá equipe interna dedicada?', default: false }),
149
- treinamento: await (0, prompts_1.confirm)({ message: 'Necessita treinamento?', default: false }),
150
- suporte: await (0, prompts_1.confirm)({ message: 'Necessita suporte mensal?', default: false }),
151
- };
152
- // ── 11. Riscos ─────────────────────────────────────────────────────────────
153
- console.log(chalk_1.default.hex('#2D7DD2').bold('\n🧠 11. Riscos Identificados\n'));
154
- const riscos = {
155
- risco_tecnico: await (0, prompts_1.input)({ message: 'Risco técnico identificado:' }),
156
- risco_juridico: await (0, prompts_1.input)({ message: 'Risco jurídico identificado:' }),
157
- risco_operacional: await (0, prompts_1.input)({ message: 'Risco operacional identificado:' }),
158
- };
159
- // ── Gerar briefing.md ──────────────────────────────────────────────────────
160
- const briefing = gerarBriefing({ geral, objetivo, escopo, regras, financeiro, metricas, plataforma, seguranca, escala, operacional, riscos });
161
- const specDir = path.join(process.cwd(), '.spec');
162
- if (!fs.existsSync(specDir))
163
- fs.mkdirSync(specDir, { recursive: true });
164
- const tasksDir = path.join(specDir, 'tasks');
165
- if (!fs.existsSync(tasksDir))
166
- fs.mkdirSync(tasksDir, { recursive: true });
167
- fs.writeFileSync(path.join(specDir, 'briefing.md'), briefing, 'utf-8');
168
- console.log(chalk_1.default.green.bold('\n✅ Briefing salvo em .spec/briefing.md\n'));
169
- console.log(chalk_1.default.gray(' Próximo passo: ') + chalk_1.default.white('vireum-spec distill') + chalk_1.default.gray(' para gerar os arquivos de spec\n'));
170
- }
171
- function gerarBriefing(data) {
172
- const { geral, objetivo, escopo, regras, financeiro, metricas, plataforma, seguranca, escala, operacional, riscos } = data;
173
- const features = escopo.features_obrigatorias
174
- .split(',').map((f) => `- [ ] ${f.trim()}`).join('\n');
175
- const featuresDesejaveis = escopo.features_desejaveis
176
- .split(',').map((f) => `- [ ] ${f.trim()}`).join('\n');
177
- return `# Briefing — ${geral.projeto}
178
-
179
- ## 📌 1. Informações Gerais
180
- - **Projeto:** ${geral.projeto}
181
- - **Cliente:** ${geral.cliente}
182
- - **Responsável:** ${geral.responsavel}
183
- - **Data da reunião:** ${geral.data}
184
- - **Versão:** 1.0
185
-
186
- ## 🎯 2. Objetivo do Projeto
187
- **Objetivo principal:** ${objetivo.objetivo}
188
-
189
- **Problema que o sistema resolve:** ${objetivo.problema}
190
-
191
- **Resultados esperados:**
192
- - Curto prazo: ${objetivo.curto_prazo}
193
- - Médio prazo: ${objetivo.medio_prazo}
194
- - Longo prazo: ${objetivo.longo_prazo}
195
-
196
- **Prioridade do cliente (o que precisa funcionar primeiro):** ${objetivo.prioridade_cliente}
197
-
198
- ## 🧩 3. Escopo Funcional
199
-
200
- ### Público do Sistema
201
- - Quem usará: ${escopo.usuarios}
202
- - Diferentes tipos de usuários: ${escopo.multi_usuario ? 'Sim' : 'Não'}
203
- - Níveis de acesso: ${escopo.niveis_acesso ? 'Sim' : 'Não'}
204
-
205
- ### Funcionalidades Obrigatórias
206
- ${features}
207
-
208
- ### Funcionalidades Desejáveis (não obrigatórias no MVP)
209
- ${featuresDesejaveis}
210
-
211
- ### Integrações Externas
212
- ${escopo.integracoes || 'Nenhuma identificada'}
213
-
214
- ## 🏗️ 4. Regras de Negócio
215
- ${regras.regras}
216
-
217
- **Exceções:** ${regras.excecoes || 'Nenhuma'}
218
-
219
- ## 💰 5. Modelo Financeiro
220
- - Integração com pagamento: ${financeiro.pagamento ? 'Sim' : 'Não'}
221
- - Modelo de cobrança: ${financeiro.cobranca}
222
- - Regras de estorno/cancelamento: ${financeiro.estorno ? 'Sim' : 'Não'}
223
-
224
- ## 📊 6. Métricas e Indicadores
225
- - KPIs: ${metricas.kpis}
226
- - Dashboard administrativo: ${metricas.dashboard ? 'Sim' : 'Não'}
227
- - Exportação de relatórios: ${metricas.relatorios ? 'Sim' : 'Não'}
228
-
229
- ## 📱 7. Plataforma e Infraestrutura
230
- - Tipo: ${plataforma.tipo}
231
- - App futuro: ${plataforma.app_futuro ? 'Sim' : 'Não'}
232
- - Notificações push: ${plataforma.push ? 'Sim' : 'Não'}
233
- - Hospedagem: ${plataforma.hospedagem || 'A definir'}
234
- - CI/CD desde o início: ${plataforma.cicd ? 'Sim' : 'Não'}
235
- - Ambiente de staging: ${plataforma.staging ? 'Sim' : 'Não'}
236
- - Domínio configurado: ${plataforma.dominio ? 'Sim' : 'Não'}
237
- - Backup e SLA: ${plataforma.backup_sla ? 'Sim' : 'Não'}
238
-
239
- ## 🔐 8. Segurança e Jurídico
240
- - Dados sensíveis: ${seguranca.dados_sensiveis ? 'Sim' : 'Não'}
241
- - LGPD: ${seguranca.lgpd ? 'Sim' : 'Não'}
242
- - Termos de uso: ${seguranca.termos ? 'Sim' : 'Não'}
243
- - Controle de acesso avançado: ${seguranca.acesso_avancado ? 'Sim' : 'Não'}
244
-
245
- ## 📈 9. Escala e Crescimento
246
- - Usuários no lançamento: ${escala.usuarios_lancamento}
247
- - Crescimento em 1 ano: ${escala.crescimento_1ano}
248
- - Padrão de uso: ${escala.uso}
249
-
250
- ## ⚙️ 10. Operacional
251
- - Operador: ${operacional.operador}
252
- - Equipe interna: ${operacional.equipe ? 'Sim' : 'Não'}
253
- - Treinamento: ${operacional.treinamento ? 'Sim' : 'Não'}
254
- - Suporte mensal: ${operacional.suporte ? 'Sim' : 'Não'}
255
-
256
- ## 🧠 11. Riscos Identificados
257
- - Risco técnico: ${riscos.risco_tecnico || 'Nenhum identificado'}
258
- - Risco jurídico: ${riscos.risco_juridico || 'Nenhum identificado'}
259
- - Risco operacional: ${riscos.risco_operacional || 'Nenhum identificado'}
260
- `;
8
+ const system_1 = require("./init/system");
9
+ const web_1 = require("./init/web");
10
+ const api_1 = require("./init/api");
11
+ const mobile_1 = require("./init/mobile");
12
+ const automation_1 = require("./init/automation");
13
+ const ai_1 = require("./init/ai");
14
+ const TIPOS_VALIDOS = ['system', 'web', 'api', 'mobile', 'automation', 'ai'];
15
+ async function runInit(type = 'system') {
16
+ if (!TIPOS_VALIDOS.includes(type)) {
17
+ console.log(chalk_1.default.red(`\n Tipo inválido: "${type}"\n`));
18
+ console.log(chalk_1.default.gray(' Tipos disponíveis: ') + chalk_1.default.white(TIPOS_VALIDOS.join(', ')) + '\n');
19
+ process.exit(1);
20
+ }
21
+ switch (type) {
22
+ case 'web': return (0, web_1.runInitWeb)();
23
+ case 'api': return (0, api_1.runInitApi)();
24
+ case 'mobile': return (0, mobile_1.runInitMobile)();
25
+ case 'automation': return (0, automation_1.runInitAutomation)();
26
+ case 'ai': return (0, ai_1.runInitAi)();
27
+ default: return (0, system_1.runInitSystem)();
28
+ }
261
29
  }
@@ -50,13 +50,17 @@ async function runPrioritize() {
50
50
  const backlogPath = path.join(tasksDir, 'backlog.md');
51
51
  const activePath = path.join(tasksDir, 'active.md');
52
52
  const indexPath = path.join(specDir, 'INDEX.md');
53
+ const requirementsPath = path.join(specDir, 'requirements.md');
53
54
  if (!fs.existsSync(backlogPath)) {
54
55
  console.log(chalk_1.default.red('\n❌ tasks/backlog.md não encontrado.'));
55
56
  console.log(chalk_1.default.gray(' Execute primeiro: ') + chalk_1.default.white('vireum-spec distill\n'));
56
57
  process.exit(1);
57
58
  }
58
59
  const backlog = fs.readFileSync(backlogPath, 'utf-8');
59
- const features = extrairFeatures(backlog);
60
+ const requirements = fs.existsSync(requirementsPath) ? fs.readFileSync(requirementsPath, 'utf-8') : '';
61
+ const featuresFromBacklog = extrairFeaturesBacklog(backlog);
62
+ const featuresFromReqs = extrairFeaturesRequirements(requirements);
63
+ const features = mergeDuplicateFeatures(featuresFromReqs, featuresFromBacklog);
60
64
  if (features.length === 0) {
61
65
  console.log(chalk_1.default.yellow('\n⚠️ Nenhuma feature encontrada no backlog.\n'));
62
66
  process.exit(0);
@@ -113,15 +117,13 @@ async function runPrioritize() {
113
117
  console.log('');
114
118
  }
115
119
  const s1 = (0, ora_1.default)('Gerando tasks/active.md...').start();
116
- await sleep(400);
117
- fs.writeFileSync(activePath, gerarActive(mvp), 'utf-8');
120
+ const maxExistingId = extrairMaxTaskId(activePath);
121
+ fs.writeFileSync(activePath, gerarActive(mvp, maxExistingId), 'utf-8');
118
122
  s1.succeed('tasks/active.md gerado');
119
123
  const s2 = (0, ora_1.default)('Atualizando tasks/backlog.md...').start();
120
- await sleep(400);
121
124
  fs.writeFileSync(backlogPath, gerarBacklog(fase2), 'utf-8');
122
125
  s2.succeed('tasks/backlog.md atualizado');
123
126
  const s3 = (0, ora_1.default)('Atualizando INDEX.md...').start();
124
- await sleep(400);
125
127
  atualizarIndex(indexPath, mvp);
126
128
  s3.succeed('INDEX.md atualizado');
127
129
  console.log(chalk_1.default.green.bold('\n✅ Priorização concluída!\n'));
@@ -136,41 +138,80 @@ async function runPrioritize() {
136
138
  }
137
139
  }
138
140
  // ─── HELPERS ──────────────────────────────────────────────────────────────────
139
- function extrairFeatures(backlog) {
141
+ function extrairFeaturesBacklog(backlog) {
140
142
  const linhas = backlog.split('\n').filter(l => l.includes('[FEATURE-'));
141
143
  return linhas.map(linha => {
142
144
  const idMatch = linha.match(/\[FEATURE-(T\d+)\]/);
143
- const nomeMatch = linha.match(/\[FEATURE-T\d+\] (.+?) \[/);
145
+ const nomeMatch = linha.match(/\[FEATURE-T\d+\]\s+([^\[]+)/);
144
146
  return {
145
147
  id: idMatch ? idMatch[1] : 'T000',
146
148
  nome: nomeMatch ? nomeMatch[1].trim() : 'feature',
147
149
  };
148
- }).filter((f) => f.nome);
150
+ }).filter((f) => f.nome && f.id !== 'T000');
149
151
  }
150
- function gerarActive(mvp) {
152
+ function extrairFeaturesRequirements(requirements) {
153
+ const mvpSection = requirements.split('## Funcionalidades — MVP')[1] || '';
154
+ if (!mvpSection)
155
+ return [];
156
+ const linhas = mvpSection.split('\n').filter(l => l.includes('R-') && (l.includes('- [ ]') || l.includes('- [x]')));
157
+ return linhas.map(linha => {
158
+ const idMatch = linha.match(/R-(\d+)/);
159
+ const nomeMatch = linha.match(/R-\d+\s+([^\[]+)/);
160
+ return {
161
+ id: idMatch ? `R${idMatch[1]}` : 'R000',
162
+ nome: nomeMatch ? nomeMatch[1].trim() : 'feature',
163
+ };
164
+ }).filter((f) => f.nome && f.id !== 'R000');
165
+ }
166
+ function mergeDuplicateFeatures(reqs, backlog) {
167
+ const nomeMap = new Map();
168
+ reqs.forEach(f => nomeMap.set(f.nome.toLowerCase(), f));
169
+ backlog.forEach(f => {
170
+ if (!nomeMap.has(f.nome.toLowerCase())) {
171
+ nomeMap.set(f.nome.toLowerCase(), f);
172
+ }
173
+ });
174
+ return Array.from(nomeMap.values());
175
+ }
176
+ function extrairMaxTaskId(activePath) {
177
+ if (!fs.existsSync(activePath))
178
+ return 0;
179
+ const content = fs.readFileSync(activePath, 'utf-8');
180
+ const matches = content.match(/\[.+-T(\d+)\]/g) || [];
181
+ if (matches.length === 0)
182
+ return 0;
183
+ return Math.max(...matches.map(m => {
184
+ const numMatch = m.match(/T(\d+)/);
185
+ return numMatch ? parseInt(numMatch[1], 10) : 0;
186
+ }));
187
+ }
188
+ function gerarActive(mvp, startFromId = 0) {
151
189
  if (mvp.length === 0) {
152
190
  return `# Tasks — Active\n\n> Nenhuma task no MVP ainda.\n> Execute vireum-spec prioritize para classificar as features.\n`;
153
191
  }
154
192
  const tasks = mvp.map((item, i) => {
155
193
  const prefix = item.nome.replace(/\s+/g, '_').toUpperCase().slice(0, 8);
156
- const num = String(i + 1).padStart(3, '0');
157
- return `## [${prefix}-T${num}] ${item.nome}
158
- - **Origem:** tasks/backlog.md FEATURE-${item.id}
159
- - **Camada:** ${item.camada}
160
- - **Complexidade:** ${item.complexidade}
161
- - **Critérios de aceitação:**
162
- ${item.criterios ? item.criterios.split(',').map((c) => ` - ${c.trim()}`).join('\n') : ' - (definir antes de implementar)'}
163
- - **Status:** [ ] Não iniciada
194
+ const num = String(startFromId + i + 1).padStart(3, '0');
195
+ const criteriosArray = item.criterios
196
+ ? item.criterios.split(',').map((c) => c.trim()).filter(Boolean)
197
+ : [];
198
+ return `## [${prefix}-T${num}] ${item.nome}
199
+ - **Origem:** FEATURE-${item.id}
200
+ - **Camada:** ${item.camada}
201
+ - **Complexidade:** ${item.complexidade}
202
+ - **Critérios de aceitação:**
203
+ ${criteriosArray.length > 0 ? criteriosArray.map((c) => ` - ${c}`).join('\n') : ' - (definir antes de implementar)'}
204
+ - **Status:** [ ] Não iniciada
164
205
  `;
165
206
  }).join('\n');
166
- return `# Tasks — Active
167
-
168
- > Tarefas do MVP prontas para desenvolvimento.
169
- > A IA só implementa tasks desta lista.
170
- > Ao concluir uma task: marcar status, mover para done.md, atualizar INDEX.md
171
-
172
- ---
173
-
207
+ return `# Tasks — Active
208
+
209
+ > Tarefas do MVP prontas para desenvolvimento.
210
+ > A IA só implementa tasks desta lista.
211
+ > Ao concluir uma task: marcar status, mover para done.md, atualizar INDEX.md
212
+
213
+ ---
214
+
174
215
  ${tasks}`;
175
216
  }
176
217
  function gerarBacklog(fase2) {
@@ -178,25 +219,32 @@ function gerarBacklog(fase2) {
178
219
  return `# Tasks — Backlog\n\n> Nenhuma feature na Fase 2 no momento.\n`;
179
220
  }
180
221
  const items = fase2.map(item => `- [ ] [FEATURE-${item.id}] ${item.nome} — ${item.complexidade} — ${item.camada} [FASE 2]`).join('\n');
181
- return `# Tasks — Backlog
182
-
183
- > Features da Fase 2. Aguardando conclusão do MVP para priorização.
184
-
185
- ${items}
222
+ return `# Tasks — Backlog
223
+
224
+ > Features da Fase 2. Aguardando conclusão do MVP para priorização.
225
+
226
+ ${items}
186
227
  `;
187
228
  }
188
229
  function atualizarIndex(indexPath, mvp) {
189
230
  if (!fs.existsSync(indexPath))
190
231
  return;
191
232
  const content = fs.readFileSync(indexPath, 'utf-8');
233
+ const maxId = extrairMaxTaskIdFromContent(content);
192
234
  const tasks = mvp.map((item, i) => {
193
235
  const prefix = item.nome.replace(/\s+/g, '_').toUpperCase().slice(0, 8);
194
- const num = String(i + 1).padStart(3, '0');
236
+ const num = String(maxId + i + 1).padStart(3, '0');
195
237
  return `- [ ] [${prefix}-T${num}] ${item.nome}`;
196
238
  }).join('\n');
197
- const updated = content.replace(/## Estado do MVP[\s\S]*?(?=\n## )/, `## Estado do MVP\n${tasks}\n\n`);
239
+ const updated = content.replace(/## Estado(?: do MVP)?[\s\S]*?(?=\n## |$)/, `## Estado\n${tasks}\n\n`);
198
240
  fs.writeFileSync(indexPath, updated, 'utf-8');
199
241
  }
200
- function sleep(ms) {
201
- return new Promise(r => setTimeout(r, ms));
242
+ function extrairMaxTaskIdFromContent(content) {
243
+ const matches = content.match(/\[.+-T(\d+)\]/g) || [];
244
+ if (matches.length === 0)
245
+ return 0;
246
+ return Math.max(...matches.map(m => {
247
+ const numMatch = m.match(/T(\d+)/);
248
+ return numMatch ? parseInt(numMatch[1], 10) : 0;
249
+ }));
202
250
  }