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.
- package/dist/commands/retrofit.js +47 -33
- package/dist/commands/setup.js +39 -15
- package/package.json +1 -1
|
@@ -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
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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() && !
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
rotas.
|
|
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
|
|
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() && !
|
|
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
|
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -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:
|
|
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
|
|
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
|
-
-
|
|
304
|
-
-
|
|
305
|
-
-
|
|
306
|
-
-
|
|
307
|
-
-
|
|
308
|
-
-
|
|
309
|
-
-
|
|
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.
|
|
343
|
-
4.
|
|
344
|
-
5.
|
|
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
|
|