@semacode/cli 1.5.7 → 1.5.9

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/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
2
+ import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { spawnSync } from "node:child_process";
5
5
  import { createRequire } from "node:module";
@@ -17,6 +17,7 @@ import { gerarCss } from "@sema/gerador-css";
17
17
  import { carregarConfiguracaoProjeto, carregarProjeto, resolverAlvoPadrao, resolverAlvosVerificacao, resolverEstruturaSaidaPadrao, resolverFrameworkPadrao, resolverSaidaPadrao, } from "./projeto.js";
18
18
  import { importarProjetoLegado, resumoImportacao } from "./importador.js";
19
19
  import { analisarDriftLegado, assistirRenomeacaoSemantica, gerarMapaImpactoSemantico, } from "./drift.js";
20
+ import { resolverDocumentacaoObrigatoria, verificarDocumentacaoMudanca, } from "./docs.js";
20
21
  const STARTER_IA = `Voce esta trabalhando com Sema, um Protocolo de Governanca de Intencao para IA sobre software vivo em backend e front consumer.
21
22
 
22
23
  Importante:
@@ -63,8 +64,9 @@ Comandos essenciais:
63
64
  - validacao: \`sema validar <arquivo.sema> --json\`
64
65
  - diagnosticos: \`sema diagnosticos <arquivo.sema> --json\`
65
66
  - formatacao: \`sema formatar <arquivo.sema>\`
66
- - importacao assistida de legado: \`sema importar <nestjs|fastapi|flask|nextjs|nextjs-consumer|react-vite-consumer|angular-consumer|flutter-consumer|firebase|dotnet|java|go|rust|cpp|typescript|python|dart> <diretorio> --saida <diretorio>\`
67
- - geracao de codigo: \`sema compilar <arquivo-ou-pasta> --alvo <typescript|python|dart|lua> --saida <diretorio>\`
67
+ - importacao assistida de legado: \`sema importar <nestjs|fastapi|flask|nextjs|nextjs-consumer|react-vite-consumer|angular-consumer|flutter-consumer|firebase|dotnet|java|go|rust|cpp|typescript|python|dart> <diretorio> --saida <diretorio>\`
68
+ - exemplos oficiais no projeto: \`sema instalar-exemplos\`
69
+ - geracao de codigo: \`sema compilar <arquivo-ou-pasta> --alvo <typescript|python|dart|lua> --saida <diretorio>\`
68
70
  - verificacao final: \`sema verificar <arquivo-ou-pasta> [--json]\`
69
71
 
70
72
  Antes de editar:
@@ -339,6 +341,12 @@ const DOCUMENTOS_SUPORTE_IA = [
339
341
  "docs/persistencia-vendor-first.md",
340
342
  "docs/sintaxe.md",
341
343
  "docs/cli.md",
344
+ "docs/mcp.md",
345
+ "docs/documentacao.md",
346
+ "docs/deploy.md",
347
+ "docs/env.md",
348
+ "docs/rollback.md",
349
+ "docs/extensao-vscode.md",
342
350
  ];
343
351
  function resolverImportadorNodeOpcional(especificador) {
344
352
  try {
@@ -350,6 +358,65 @@ function resolverImportadorNodeOpcional(especificador) {
350
358
  }
351
359
  const TSX_IMPORTADOR_CLI = resolverImportadorNodeOpcional("tsx");
352
360
  const TSX_EXECUTOR_CLI = resolverImportadorNodeOpcional("tsx/cli");
361
+ async function caminhoEhDiretorio(caminhoAlvo) {
362
+ try {
363
+ return (await stat(caminhoAlvo)).isDirectory();
364
+ }
365
+ catch {
366
+ return false;
367
+ }
368
+ }
369
+ async function localizarDiretorioExemplosOficiais() {
370
+ const candidatos = [
371
+ path.resolve(DIRETORIO_CLI_ATUAL, "..", "exemplos"),
372
+ path.resolve(DIRETORIO_CLI_ATUAL, "..", "..", "..", "exemplos"),
373
+ ];
374
+ for (const candidato of candidatos) {
375
+ if (await caminhoEhDiretorio(candidato)) {
376
+ return candidato;
377
+ }
378
+ }
379
+ return undefined;
380
+ }
381
+ async function materializarExemplosOficiais(baseProjeto = process.cwd(), preservarExistentes = true) {
382
+ const origem = await localizarDiretorioExemplosOficiais();
383
+ const destino = path.resolve(baseProjeto, "exemplos");
384
+ if (!origem) {
385
+ return {
386
+ sucesso: false,
387
+ origem: null,
388
+ destino,
389
+ criados: [],
390
+ preservados: [],
391
+ erro: "Diretorio de exemplos oficiais nao foi encontrado no pacote da CLI.",
392
+ };
393
+ }
394
+ await mkdir(destino, { recursive: true });
395
+ const criados = [];
396
+ const preservados = [];
397
+ const entradas = await readdir(origem, { withFileTypes: true });
398
+ for (const entrada of entradas.sort((a, b) => a.name.localeCompare(b.name, "pt-BR"))) {
399
+ if (!entrada.isFile() || !entrada.name.endsWith(".sema")) {
400
+ continue;
401
+ }
402
+ const origemArquivo = path.join(origem, entrada.name);
403
+ const destinoArquivo = path.join(destino, entrada.name);
404
+ const jaExiste = await caminhoExiste(destinoArquivo);
405
+ if (jaExiste && preservarExistentes) {
406
+ preservados.push(entrada.name);
407
+ continue;
408
+ }
409
+ await writeFile(destinoArquivo, await readFile(origemArquivo, "utf8"), "utf8");
410
+ criados.push(entrada.name);
411
+ }
412
+ return {
413
+ sucesso: true,
414
+ origem,
415
+ destino,
416
+ criados,
417
+ preservados,
418
+ };
419
+ }
353
420
  function obterArgumentos() {
354
421
  const [, , comando, ...resto] = process.argv;
355
422
  return { comando: comando, resto };
@@ -389,6 +456,7 @@ function ajuda() {
389
456
  "sema resumo <arquivo-ou-pasta> --micro --para mudanca",
390
457
  "sema drift <arquivo-ou-pasta> --escopo modulo --json",
391
458
  "sema impacto <arquivo-ou-pasta> --alvo <token> --mudanca <descricao> --json",
459
+ "sema docs-impacto --intencao \"fazer deploy\" --criar-ausentes --json",
392
460
  "sema contexto-ia <arquivo.sema> --saida ./.tmp/contexto --json",
393
461
  "",
394
462
  "[3] Adotar Sema em projeto que ainda nao usa",
@@ -430,6 +498,9 @@ function ajuda() {
430
498
  "sema exemplos-prompt-ia",
431
499
  "sema contexto-ia <arquivo.sema> [--saida <diretorio>] [--json]",
432
500
  "sema sync-ai-entrypoints [--json]",
501
+ "sema instalar-exemplos [--json]",
502
+ "sema docs-impacto --intencao <acao> [--arquivo <caminho>] [--criar-ausentes] [--json]",
503
+ "sema finalizar-mudanca --intencao <acao> [--arquivo <caminho>] [--doc-lida <caminho>] [--json]",
433
504
  ]),
434
505
  "",
435
506
  renderizarSecaoAscii("Operacional", [
@@ -461,6 +532,16 @@ function obterOpcao(args, nome, padrao) {
461
532
  function possuiFlag(args, nome) {
462
533
  return args.includes(nome);
463
534
  }
535
+ function obterOpcoesRepetidas(args, nome) {
536
+ const valores = [];
537
+ for (let indice = 0; indice < args.length; indice += 1) {
538
+ if (args[indice] === nome && args[indice + 1]) {
539
+ valores.push(args[indice + 1]);
540
+ indice += 1;
541
+ }
542
+ }
543
+ return valores;
544
+ }
464
545
  const OPCOES_COM_VALOR = new Set([
465
546
  "--template",
466
547
  "--alvo", "-a",
@@ -472,6 +553,9 @@ const OPCOES_COM_VALOR = new Set([
472
553
  "--mudanca",
473
554
  "--de",
474
555
  "--para",
556
+ "--intencao",
557
+ "--arquivo",
558
+ "--doc-lida",
475
559
  ]);
476
560
  const ALIAS_OPCOES = {
477
561
  "-a": "--alvo",
@@ -3177,9 +3261,113 @@ public:
3177
3261
  ];
3178
3262
  }
3179
3263
  await escreverArquivos(cwd, arquivos);
3264
+ const exemplos = await materializarExemplosOficiais(cwd, true);
3265
+ if (!exemplos.sucesso) {
3266
+ console.error(exemplos.erro);
3267
+ return 1;
3268
+ }
3180
3269
  console.log(`Projeto Sema inicializado com template ${template}.`);
3270
+ console.log(`Exemplos oficiais sincronizados em ${exemplos.destino} (${exemplos.criados.length} criados, ${exemplos.preservados.length} preservados).`);
3271
+ return 0;
3272
+ }
3273
+ async function comandoInstalarExemplos(emJson) {
3274
+ const resultado = await materializarExemplosOficiais(process.cwd(), true);
3275
+ const payload = {
3276
+ comando: "instalar-exemplos",
3277
+ ...resultado,
3278
+ };
3279
+ if (emJson) {
3280
+ console.log(JSON.stringify(payload, null, 2));
3281
+ return resultado.sucesso ? 0 : 1;
3282
+ }
3283
+ if (!resultado.sucesso) {
3284
+ console.error(resultado.erro ?? "Falha ao instalar exemplos oficiais.");
3285
+ return 1;
3286
+ }
3287
+ console.log("Exemplos oficiais sincronizados");
3288
+ console.log(`- Origem: ${resultado.origem}`);
3289
+ console.log(`- Destino: ${resultado.destino}`);
3290
+ console.log(`- Criados: ${resultado.criados.length}`);
3291
+ console.log(`- Preservados: ${resultado.preservados.length}`);
3181
3292
  return 0;
3182
3293
  }
3294
+ function resolverEntradaDocs(posicionais, args) {
3295
+ const intencao = obterOpcao(args, "--intencao") ?? posicionais[0] ?? "";
3296
+ const arquivosPorFlag = obterOpcoesRepetidas(args, "--arquivo");
3297
+ const arquivosPosicionais = obterOpcao(args, "--intencao") ? posicionais : posicionais.slice(1);
3298
+ return {
3299
+ intencao,
3300
+ arquivosAlvo: [...new Set([...arquivosPorFlag, ...arquivosPosicionais])],
3301
+ };
3302
+ }
3303
+ async function comandoDocsImpacto(posicionais, args, emJson) {
3304
+ const { intencao, arquivosAlvo } = resolverEntradaDocs(posicionais, args);
3305
+ if (!intencao.trim()) {
3306
+ console.error("Uso: sema docs-impacto --intencao <acao> [--arquivo <caminho>] [--criar-ausentes] [--json]");
3307
+ return 1;
3308
+ }
3309
+ const resultado = await resolverDocumentacaoObrigatoria({
3310
+ baseProjeto: process.cwd(),
3311
+ intencao,
3312
+ arquivosAlvo,
3313
+ criarAusentes: possuiFlag(args, "--criar-ausentes"),
3314
+ });
3315
+ const payload = {
3316
+ comando: "docs-impacto",
3317
+ ...resultado,
3318
+ };
3319
+ if (emJson) {
3320
+ console.log(JSON.stringify(payload, null, 2));
3321
+ return resultado.sucesso ? 0 : 1;
3322
+ }
3323
+ console.log("Documentacao obrigatoria da mudanca");
3324
+ console.log(`- Intencao: ${resultado.intencao}`);
3325
+ console.log(`- Categorias: ${resumirListaTexto(resultado.categorias, 8)}`);
3326
+ console.log(`- Leitura obrigatoria: ${resultado.leituraObrigatoria.length}`);
3327
+ for (const doc of resultado.leituraObrigatoria) {
3328
+ console.log(` - ${doc.relativo} (${doc.existe ? "ok" : "ausente"}) - ${doc.motivo}`);
3329
+ }
3330
+ if (resultado.docsCriadas.length > 0) {
3331
+ console.log(`- Docs criadas: ${resultado.docsCriadas.map((doc) => doc.relativo).join(", ")}`);
3332
+ }
3333
+ if (resultado.bloqueios.length > 0) {
3334
+ console.error("Bloqueios:");
3335
+ for (const bloqueio of resultado.bloqueios) {
3336
+ console.error(`- [SEM DOC ${bloqueio.severidade}] ${bloqueio.mensagem}`);
3337
+ }
3338
+ }
3339
+ return resultado.sucesso ? 0 : 1;
3340
+ }
3341
+ async function comandoFinalizarMudanca(posicionais, args, emJson) {
3342
+ const { intencao, arquivosAlvo } = resolverEntradaDocs(posicionais, args);
3343
+ if (!intencao.trim()) {
3344
+ console.error("Uso: sema finalizar-mudanca --intencao <acao> [--arquivo <caminho>] [--doc-lida <caminho>] [--json]");
3345
+ return 1;
3346
+ }
3347
+ const resultado = await verificarDocumentacaoMudanca({
3348
+ baseProjeto: process.cwd(),
3349
+ intencao,
3350
+ arquivosAlvo,
3351
+ docsLidas: obterOpcoesRepetidas(args, "--doc-lida"),
3352
+ });
3353
+ const payload = {
3354
+ comando: "finalizar-mudanca",
3355
+ ...resultado,
3356
+ };
3357
+ if (emJson) {
3358
+ console.log(JSON.stringify(payload, null, 2));
3359
+ return resultado.sucesso ? 0 : 1;
3360
+ }
3361
+ if (resultado.sucesso) {
3362
+ console.log("Leitura documental comprovada para a mudanca.");
3363
+ return 0;
3364
+ }
3365
+ console.error("Mudanca bloqueada por documentacao obrigatoria:");
3366
+ for (const diagnostico of resultado.diagnosticos) {
3367
+ console.error(`- [severidade ${diagnostico.severidade}] ${diagnostico.mensagem}`);
3368
+ }
3369
+ return 1;
3370
+ }
3183
3371
  async function comandoValidar(entrada) {
3184
3372
  const modulos = await carregarModulos(entrada);
3185
3373
  const diagnosticos = modulos.flatMap((item) => item.resultado.diagnosticos);
@@ -3639,10 +3827,26 @@ async function comandoStarterIa() {
3639
3827
  }
3640
3828
  async function comandoSyncAiEntrypoints(emJson) {
3641
3829
  const resumoProjeto = await gerarResumoProjetoIa(process.cwd(), undefined, true);
3830
+ const exemplos = await materializarExemplosOficiais(resumoProjeto.baseProjeto, true);
3831
+ if (!exemplos.sucesso) {
3832
+ if (emJson) {
3833
+ console.log(JSON.stringify({
3834
+ comando: "sync-ai-entrypoints",
3835
+ sucesso: false,
3836
+ baseProjeto: resumoProjeto.baseProjeto,
3837
+ erro: exemplos.erro,
3838
+ exemplos,
3839
+ }, null, 2));
3840
+ return 1;
3841
+ }
3842
+ console.error(exemplos.erro);
3843
+ return 1;
3844
+ }
3642
3845
  const indexJson = JSON.parse(await readFile(path.join(resumoProjeto.pastaSaida, "SEMA_INDEX.json"), "utf8"));
3643
3846
  const artefatos = [...new Set([
3644
3847
  ...ARQUIVOS_CANONICOS_IA_RAIZ,
3645
3848
  ...resumoProjeto.artefatos,
3849
+ "exemplos",
3646
3850
  ])];
3647
3851
  if (emJson) {
3648
3852
  console.log(JSON.stringify({
@@ -3652,6 +3856,7 @@ async function comandoSyncAiEntrypoints(emJson) {
3652
3856
  pastaSaida: resumoProjeto.pastaSaida,
3653
3857
  artefatos,
3654
3858
  entradaCanonica: indexJson.entradaCanonica,
3859
+ exemplos,
3655
3860
  }, null, 2));
3656
3861
  return 0;
3657
3862
  }
@@ -3662,6 +3867,7 @@ async function comandoSyncAiEntrypoints(emJson) {
3662
3867
  console.log(`IA pequena: ${indexJson.entradaCanonica.porCapacidade.pequena.join(" -> ")}`);
3663
3868
  console.log(`IA media: ${indexJson.entradaCanonica.porCapacidade.media.join(" -> ")}`);
3664
3869
  console.log(`IA grande: ${indexJson.entradaCanonica.porCapacidade.grande.join(" -> ")}`);
3870
+ console.log(`Exemplos oficiais: ${exemplos.criados.length} criados, ${exemplos.preservados.length} preservados em ${exemplos.destino}`);
3665
3871
  return 0;
3666
3872
  }
3667
3873
  async function comandoAjudaIa() {
@@ -3690,12 +3896,15 @@ async function comandoAjudaIa() {
3690
3896
  console.log(renderizarSecaoAscii("Fluxo recomendado", [
3691
3897
  "Use `sema starter-ia` para um texto curto de onboarding.",
3692
3898
  "Use `sema sync-ai-entrypoints` para regenerar `SEMA_BRIEF.*` e `SEMA_INDEX.json` na raiz.",
3899
+ "Use `sema instalar-exemplos` para materializar `exemplos/` oficiais sem sobrescrever arquivos locais.",
3693
3900
  "Use `sema resumo <arquivo> --micro --para onboarding` para IA pequena.",
3694
3901
  "Use `sema prompt-curto <arquivo> --curto --para mudanca` para colar contexto em modelo gratuito.",
3695
3902
  "Use `sema prompt-ia`, `sema prompt-ia-ui`, `sema prompt-ia-react` e `sema prompt-ia-sema-primeiro` conforme a tarefa.",
3696
3903
  "Use `sema exemplos-prompt-ia` para pegar modelos prontos de prompt.",
3697
3904
  "Use `sema inspecionar` para descobrir base, codigo vivo e fontes legado.",
3698
3905
  "Use `sema drift` para medir impls, vinculos, rotas, score e lacunas.",
3906
+ "Use `sema docs-impacto --intencao <acao>` para ler ou criar docs obrigatorias antes de agir.",
3907
+ "Use `sema finalizar-mudanca --intencao <acao>` para bloquear conclusao sem leitura documental comprovada.",
3699
3908
  "Use `sema contexto-ia <arquivo.sema>` para gerar AST, IR, drift, `briefing.json` e `briefing.min.json`.",
3700
3909
  "Use `sema compilar <arquivo-ou-pasta> --alvo <typescript|python|dart|lua> --saida <diretorio>` quando a tarefa pedir codigo derivado.",
3701
3910
  ]));
@@ -4201,6 +4410,15 @@ async function principal() {
4201
4410
  case "sync-ai-entrypoints":
4202
4411
  codigoSaida = await comandoSyncAiEntrypoints(possuiFlag(resto, "--json"));
4203
4412
  break;
4413
+ case "instalar-exemplos":
4414
+ codigoSaida = await comandoInstalarExemplos(possuiFlag(resto, "--json"));
4415
+ break;
4416
+ case "docs-impacto":
4417
+ codigoSaida = await comandoDocsImpacto(posicionais, resto, possuiFlag(resto, "--json"));
4418
+ break;
4419
+ case "finalizar-mudanca":
4420
+ codigoSaida = await comandoFinalizarMudanca(posicionais, resto, possuiFlag(resto, "--json"));
4421
+ break;
4204
4422
  case "resumo":
4205
4423
  codigoSaida = await comandoResumo(posicionais[0], resto, possuiFlag(resto, "--json"));
4206
4424
  break;