vireum-spec-cli 0.2.0 → 0.3.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.
@@ -114,11 +114,11 @@ async function runRetrofit() {
114
114
  default: false,
115
115
  });
116
116
  const featuresAtivas = await (0, prompts_1.input)({
117
- message: 'Liste as funcionalidades JA implementadas (separadas por virgula):',
118
- default: analise.features.join(', '),
117
+ message: 'Liste as funcionalidades JA implementadas (separadas por virgula — Enter para pular):',
118
+ default: analise.features.length ? analise.features.join(', ') : '',
119
119
  });
120
120
  const featuresPendentes = await (0, prompts_1.input)({
121
- message: 'Liste as funcionalidades PENDENTES ou em desenvolvimento:',
121
+ message: 'Liste as funcionalidades PENDENTES (separadas por virgula — Enter para pular):',
122
122
  });
123
123
  const riscos = await (0, prompts_1.input)({
124
124
  message: 'Riscos tecnicos identificados:',
@@ -128,8 +128,8 @@ async function runRetrofit() {
128
128
  const dados = {
129
129
  projeto, cliente, objetivo, problema, prioridade,
130
130
  usuarios, regras, fase, multiTenant, lgpd,
131
- featuresAtivas: featuresAtivas.split(',').map(f => f.trim()).filter(Boolean),
132
- featuresPendentes: featuresPendentes.split(',').map(f => f.trim()).filter(Boolean),
131
+ featuresAtivas: featuresAtivas ? featuresAtivas.split(',').map(f => f.trim()).filter(Boolean) : [],
132
+ featuresPendentes: featuresPendentes ? featuresPendentes.split(',').map(f => f.trim()).filter(Boolean) : [],
133
133
  riscos, analise,
134
134
  data: new Date().toLocaleDateString('pt-BR'),
135
135
  };
@@ -154,7 +154,6 @@ async function runRetrofit() {
154
154
  fs.writeFileSync(path.join(specDir, arq.nome), arq.conteudo, 'utf-8');
155
155
  s.succeed(arq.nome);
156
156
  }
157
- // tasks
158
157
  const s1 = (0, ora_1.default)('Gerando tasks...').start();
159
158
  await sleep(400);
160
159
  fs.writeFileSync(path.join(tasksDir, 'active.md'), gerarTasksActive(dados), 'utf-8');
@@ -185,7 +184,6 @@ function analisarProjeto(cwd) {
185
184
  cache: '',
186
185
  },
187
186
  };
188
- // package.json
189
187
  const pkgPath = path.join(cwd, 'package.json');
190
188
  if (fs.existsSync(pkgPath)) {
191
189
  try {
@@ -193,7 +191,6 @@ function analisarProjeto(cwd) {
193
191
  resultado.nome = pkg.name || '';
194
192
  resultado.descricao = pkg.description || '';
195
193
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
196
- // Frontend
197
194
  if (deps['next'])
198
195
  resultado.stack.frontend = 'Next.js';
199
196
  else if (deps['react'])
@@ -202,14 +199,12 @@ function analisarProjeto(cwd) {
202
199
  resultado.stack.frontend = 'Vue';
203
200
  else if (deps['nuxt'])
204
201
  resultado.stack.frontend = 'Nuxt';
205
- // Backend
206
202
  if (deps['fastify'])
207
203
  resultado.stack.backend = 'Node.js + Fastify';
208
204
  else if (deps['express'])
209
205
  resultado.stack.backend = 'Node.js + Express';
210
206
  else if (deps['@nestjs/core'])
211
207
  resultado.stack.backend = 'NestJS';
212
- // Banco
213
208
  if (deps['pg'] || deps['postgres'])
214
209
  resultado.stack.banco = 'PostgreSQL';
215
210
  else if (deps['mysql2'])
@@ -218,29 +213,25 @@ function analisarProjeto(cwd) {
218
213
  resultado.stack.banco = 'SQLite';
219
214
  else if (deps['mongoose'])
220
215
  resultado.stack.banco = 'MongoDB';
221
- // ORM
222
216
  if (deps['prisma'] || deps['@prisma/client'])
223
217
  resultado.stack.orm = 'Prisma';
224
218
  else if (deps['typeorm'])
225
219
  resultado.stack.orm = 'TypeORM';
226
220
  else if (deps['drizzle-orm'])
227
221
  resultado.stack.orm = 'Drizzle';
228
- // Auth
229
222
  if (deps['jsonwebtoken'])
230
223
  resultado.stack.auth = 'JWT';
231
224
  else if (deps['next-auth'])
232
225
  resultado.stack.auth = 'NextAuth';
233
226
  else if (deps['@clerk/nextjs'])
234
227
  resultado.stack.auth = 'Clerk';
235
- // Cache
236
- if (deps['ioredis'] || deps['redis'])
237
- resultado.stack.cache = 'Redis';
238
228
  if (deps['bullmq'])
239
229
  resultado.stack.cache = 'Redis + BullMQ';
230
+ else if (deps['ioredis'] || deps['redis'])
231
+ resultado.stack.cache = 'Redis';
240
232
  }
241
233
  catch { }
242
234
  }
243
- // README
244
235
  const readmePath = path.join(cwd, 'README.md');
245
236
  if (fs.existsSync(readmePath)) {
246
237
  resultado.readme = true;
@@ -249,41 +240,63 @@ function analisarProjeto(cwd) {
249
240
  if (desc)
250
241
  resultado.descricao = resultado.descricao || desc.trim();
251
242
  }
252
- // Rotas procura em src/
253
- const srcDir = path.join(cwd, 'src');
254
- if (fs.existsSync(srcDir)) {
255
- resultado.features = extrairRotas(srcDir);
256
- resultado.arquivos = contarArquivos(srcDir);
243
+ const pastasCodigo = ['src', 'app', 'pages', 'components', 'server', 'api'];
244
+ let totalArquivos = 0;
245
+ const todasRotas = [];
246
+ for (const pasta of pastasCodigo) {
247
+ const dir = path.join(cwd, pasta);
248
+ if (fs.existsSync(dir)) {
249
+ todasRotas.push(...extrairRotas(dir));
250
+ totalArquivos += contarArquivos(dir);
251
+ }
257
252
  }
253
+ resultado.features = [...new Set(todasRotas)].slice(0, 20);
254
+ resultado.arquivos = totalArquivos;
258
255
  return resultado;
259
256
  }
260
257
  function extrairRotas(dir) {
261
258
  const rotas = [];
262
- const exts = ['.ts', '.js'];
259
+ const exts = ['.ts', '.tsx', '.js', '.jsx'];
263
260
  function walk(d) {
264
261
  if (!fs.existsSync(d))
265
262
  return;
266
263
  for (const entry of fs.readdirSync(d)) {
267
264
  const full = path.join(d, entry);
268
265
  const stat = fs.statSync(full);
269
- if (stat.isDirectory() && !entry.includes('node_modules')) {
266
+ if (stat.isDirectory() && !['node_modules', '.next', 'dist', '.git'].includes(entry)) {
270
267
  walk(full);
271
268
  }
272
269
  else if (exts.includes(path.extname(entry))) {
273
- const content = fs.readFileSync(full, 'utf-8');
274
- const matches = content.match(/\.(get|post|put|patch|delete|route)\(['"`]([^'"`]+)/gi);
275
- if (matches) {
276
- matches.forEach(m => {
277
- const rota = m.replace(/\.(get|post|put|patch|delete|route)\(['"`]/i, '').trim();
278
- if (rota && !rotas.includes(rota))
279
- rotas.push(rota);
280
- });
270
+ try {
271
+ const content = fs.readFileSync(full, 'utf-8');
272
+ const apiMatches = content.match(/\.(get|post|put|patch|delete|route)\(['"`]([^'"`]+)/gi);
273
+ if (apiMatches) {
274
+ apiMatches.forEach(m => {
275
+ const rota = m.replace(/\.(get|post|put|patch|delete|route)\(['"`]/i, '').trim();
276
+ if (rota && !rotas.includes(rota))
277
+ rotas.push(rota);
278
+ });
279
+ }
280
+ if (entry === 'page.tsx' || entry === 'page.ts') {
281
+ const relativo = path.relative(dir, full)
282
+ .replace(/\\/g, '/')
283
+ .replace('/page.tsx', '')
284
+ .replace('/page.ts', '');
285
+ if (relativo && !rotas.includes(relativo))
286
+ rotas.push(`page: /${relativo}`);
287
+ }
288
+ if (d.includes('pages') && (entry.endsWith('.tsx') || entry.endsWith('.ts')) && !entry.startsWith('_')) {
289
+ const pagina = entry.replace('.tsx', '').replace('.ts', '');
290
+ if (!rotas.includes(pagina))
291
+ rotas.push(`page: ${pagina}`);
292
+ }
281
293
  }
294
+ catch { }
282
295
  }
283
296
  }
284
297
  }
285
298
  walk(dir);
286
- return rotas.slice(0, 20);
299
+ return rotas;
287
300
  }
288
301
  function contarArquivos(dir) {
289
302
  let count = 0;
@@ -292,8 +305,9 @@ function contarArquivos(dir) {
292
305
  return;
293
306
  for (const entry of fs.readdirSync(d)) {
294
307
  const full = path.join(d, entry);
295
- if (fs.statSync(full).isDirectory() && !entry.includes('node_modules'))
308
+ if (fs.statSync(full).isDirectory() && !['node_modules', '.next', 'dist', '.git'].includes(entry)) {
296
309
  walk(full);
310
+ }
297
311
  else
298
312
  count++;
299
313
  }
@@ -149,6 +149,7 @@ async function runSetup() {
149
149
  const mcpsSelecionados = await (0, prompts_1.checkbox)({
150
150
  message: 'Selecione os MCPs para este projeto:',
151
151
  choices: [
152
+ { name: 'context7 — docs atualizadas das libs do projeto', value: 'context7', checked: true },
152
153
  { name: 'github — PRs, issues, branches', value: 'github', checked: true },
153
154
  { name: 'database — consultar banco em dev', value: 'database', checked: true },
154
155
  { name: 'browser — testar endpoints e fluxos', value: 'browser', checked: true },
@@ -157,10 +158,14 @@ async function runSetup() {
157
158
  { name: 'redis — inspecionar cache e filas', value: 'redis', checked: false },
158
159
  ],
159
160
  });
161
+ // Context7 sempre presente
162
+ const mcpsFinais = mcpsSelecionados.includes('context7')
163
+ ? mcpsSelecionados
164
+ : ['context7', ...mcpsSelecionados];
160
165
  // ── Gerar arquivos ─────────────────────────────────────────────────────────
161
166
  console.log('');
162
167
  const projeto = extrairProjeto(path.join(specDir, 'INDEX.md'));
163
- const dados = { stack, infra, mcps: mcpsSelecionados, projeto };
168
+ const dados = { stack, infra, mcps: mcpsFinais, projeto };
164
169
  for (const dir of [vireumDir, cursorDir]) {
165
170
  if (!fs.existsSync(dir))
166
171
  fs.mkdirSync(dir, { recursive: true });
@@ -236,6 +241,7 @@ ${mcps.map((m) => `- ${m}`).join('\n')}
236
241
  }
237
242
  function gerarMcpSetup(d) {
238
243
  const mcpInfo = {
244
+ context7: 'https://github.com/upstash/context7',
239
245
  github: 'https://github.com/modelcontextprotocol/servers/tree/main/src/github',
240
246
  database: 'https://github.com/modelcontextprotocol/servers/tree/main/src/postgres',
241
247
  browser: 'https://github.com/modelcontextprotocol/servers/tree/main/src/puppeteer',
@@ -256,7 +262,7 @@ ${lista}
256
262
  1. Acesse o link de cada MCP acima
257
263
  2. Siga as instruções de instalação
258
264
  3. Configure as credenciais manualmente no seu cliente (Claude Code / Cursor)
259
- 4. Execute: vireum-spec verify-mcps (em breve)
265
+ 4. Execute: vireum-spec verify-mcps
260
266
  `;
261
267
  }
262
268
  function gerarRulesGlobal() {
@@ -300,18 +306,20 @@ function gerarMcpsGlobal(d) {
300
306
  > MCPs ativos por projeto estão em .spec/architecture.md
301
307
 
302
308
  ## Stack Padrão
303
- - filesystem leitura e escrita no projeto (nativo)
304
- - github PRs, issues, branches referenciando tasks
305
- - database validar schema e dados em desenvolvimento
306
- - browser testar endpoints e validar fluxos
307
- - puppeteer testes de jornada de UI
308
- - docker gerenciar containers em desenvolvimento
309
- - redis inspecionar cache e filas (quando aplicável)
309
+ - context7 documentação atualizada das libs (sempre ativo)
310
+ - filesystem leitura e escrita no projeto (nativo)
311
+ - github PRs, issues, branches referenciando tasks
312
+ - database validar schema e dados em desenvolvimento
313
+ - browser testar endpoints e validar fluxos
314
+ - puppeteer testes de jornada de UI
315
+ - docker gerenciar containers em desenvolvimento
316
+ - redis — inspecionar cache e filas (quando aplicável)
310
317
 
311
318
  ## MCPs Ativos Neste Projeto
312
319
  ${d.mcps.map((m) => `- ${m}`).join('\n')}
313
320
 
314
321
  ## Quando usar cada MCP
322
+ - Antes de usar qualquer lib → context7: buscar docs atualizadas
315
323
  - Task concluída → github: criar PR referenciando a task
316
324
  - Bug identificado → github: abrir issue com contexto
317
325
  - Decisão de schema → database: validar antes de implementar
@@ -323,6 +331,9 @@ function gerarClaudeMd(d) {
323
331
  const { projeto, stack, mcps } = d;
324
332
  const tenant = stack.multiTenant
325
333
  ? '\n- NUNCA fazer query sem filtro de tenantId — projeto multi-tenant' : '';
334
+ const libs = [stack.frontend, stack.backend, stack.orm, stack.cache]
335
+ .filter((l) => l && l !== 'Nenhum' && l !== 'Outro')
336
+ .join(', ');
326
337
  return `# ${projeto} — Vireum Spec Protocol
327
338
 
328
339
  > Este projeto usa Spec Driven Development pela Vireum Desenvolvimento.
@@ -339,9 +350,10 @@ function gerarClaudeMd(d) {
339
350
  Acionado por: "desenvolve", "implementa", "cria", + nome de task
340
351
  1. Leia \`.spec/tasks/active.md\`
341
352
  2. Leia \`.spec/requirements.md\` para contexto da feature
342
- 3. Implemente seguindo os critérios de aceitação da task
343
- 4. Ao concluir: marque como done, mova para \`tasks/done.md\`, atualize \`INDEX.md\`
344
- 5. Se decisão arquitetural tomada: registre em \`architecture.md\`
353
+ 3. Consulte o Context7 para docs atualizadas da lib que vai usar
354
+ 4. Implemente seguindo os critérios de aceitação da task
355
+ 5. Ao concluir: marque como done, mova para \`tasks/done.md\`, atualize \`INDEX.md\`
356
+ 6. Se decisão arquitetural tomada: registre em \`architecture.md\`
345
357
 
346
358
  ### Modo 2 — Bug
347
359
  Acionado por: "erro", "bug", "quebrou", "não funciona"
@@ -362,6 +374,11 @@ Acionado por: "como deve funcionar", "o que foi combinado", "qual o comportament
362
374
  1. Leia \`.spec/requirements.md\`
363
375
  2. Responda com base no spec — não invente comportamento
364
376
 
377
+ ## Context7 — Documentação atualizada
378
+ Sempre que for usar uma lib do projeto, consulte a documentação atualizada via Context7 antes de implementar.
379
+ Nunca assuma que você conhece a API mais recente — sempre verifique.
380
+ Libs deste projeto: ${libs}
381
+
365
382
  ## Regras globais
366
383
  Leia \`.vireum/rules.md\` — aplicam-se a todas as sessões.
367
384
 
@@ -394,10 +411,13 @@ function gerarAgentsMd(d) {
394
411
  2. Identifique o modo pela solicitação
395
412
  3. Siga o protocolo em CLAUDE.md
396
413
 
414
+ ## Context7
415
+ Sempre consultar docs atualizadas via Context7 antes de usar qualquer lib.
416
+
397
417
  ## Regras globais
398
418
  Ver \`.vireum/rules.md\`
399
419
 
400
- ## Regras do projeto
420
+ ## Regras do projeto
401
421
  Ver \`.spec/rules.md\`
402
422
 
403
423
  ## Stack
@@ -421,9 +441,13 @@ Este projeto usa Spec Driven Development pela Vireum Desenvolvimento.
421
441
  - Sempre leia \`.spec/INDEX.md\` primeiro
422
442
  - Carregue outros arquivos de spec apenas quando necessário
423
443
 
444
+ ## Context7
445
+ - Sempre consultar Context7 antes de usar qualquer lib do projeto
446
+ - Nunca assumir que conhece a API mais recente
447
+
424
448
  ## Modos
425
- - Implementar → leia tasks/active.md, siga critérios de aceitação
426
- - Bug → crie hotfix [H] em active.md, registre causa raiz em changelog.md
449
+ - Implementar → leia tasks/active.md, consulte Context7, siga critérios de aceitação
450
+ - Bug → crie hotfix [H] em active.md, registre causa raiz em changelog.md
427
451
  - Nova demanda → crie [PENDING] em backlog.md, aguarde aprovação
428
452
  - Dúvida de escopo → consulte requirements.md
429
453
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vireum-spec-cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Spec Driven Development framework by Vireum Desenvolvimento",
5
5
  "main": "dist/index.js",
6
6
  "bin": {