@semacode/cli 4.0.0 → 4.1.1

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.
@@ -428,11 +428,10 @@ function validarResumo(args, promptCurto) {
428
428
  const pacote = parsed.flags.has("--pacote");
429
429
  const somentePacote = parsed.flags.has("--somente-pacote");
430
430
  const pedido = valorOpcao(parsed, "--pedido");
431
- const combinacaoPacoteValida = pacote === Boolean(pedido);
432
431
  return tamanhos.length <= 1
433
432
  && (!modo || MODOS_RESUMO.has(modo))
434
433
  && (promptCurto || validarModoCache(parsed, "--drift"))
435
- && (promptCurto ? !pacote && !pedido && !somentePacote : combinacaoPacoteValida && (!somentePacote || pacote));
434
+ && (promptCurto ? !pacote && !pedido && !somentePacote : true);
436
435
  }
437
436
  function validarComandoBasico(comando, args) {
438
437
  const aliasesGeracao = { "-a": "--alvo", "-s": "--saida" };
@@ -102,7 +102,7 @@ export function criarAjudaRaiz(versao) {
102
102
  "fraca: sema resumo <arquivo> --micro --drift none --json (contrato compacto; implementação não avaliada)",
103
103
  "média: sema resumo <arquivo> --curto --drift none --json + sema drift <arquivo> --cache fresh --json",
104
104
  "forte: sema contexto-ia <arquivo.sema> --saida <diretorio> --json",
105
- "context-pack: sema resumo <arquivo.sema> --micro --para mudanca --drift none --pacote --pedido \"<intenção>\" --json (seleção determinística para a IA)",
105
+ "context-pack: sema resumo <arquivo.sema> --micro --para mudanca --drift none --json (compactação determinística automática; use --pedido para focar a seleção)",
106
106
  ]),
107
107
  "",
108
108
  renderizarSecaoAscii("Comandos principais", [
@@ -134,7 +134,7 @@ export function criarAjudaRaiz(versao) {
134
134
  renderizarSecaoAscii("Ajuda IA-first", [
135
135
  "sema ajuda-ia",
136
136
  "sema starter-ia",
137
- "sema resumo <arquivo-ou-pasta> [--micro|--curto|--medio] [--para <resumo|onboarding|review|mudanca|bug|arquitetura>] [--drift <none|cache|fresh>] [--saida <diretorio>] [--raiz] [--pacote --somente-pacote --pedido <intencao>] [--json]",
137
+ "sema resumo <arquivo-ou-pasta> [--micro|--curto|--medio] [--para <resumo|onboarding|review|mudanca|bug|arquitetura>] [--drift <none|cache|fresh>] [--saida <diretorio>] [--raiz] [--pedido <intencao>] [--somente-pacote] [--json]",
138
138
  "sema prompt-curto <arquivo-ou-pasta> [--micro|--curto|--medio] [--para <resumo|onboarding|review|mudanca|bug|arquitetura>] [--json]",
139
139
  "sema prompt-ia",
140
140
  "sema prompt-ia-ui",
@@ -65,7 +65,7 @@ export type ContextPackResult = {
65
65
  export declare function selecionarContexto(payload: ContextPackPayload, request: string): ContextPackResult;
66
66
  export declare function criarContextPackAPartirDoResumo(input: {
67
67
  resumo: ResumoSemanticoModuloIa;
68
- pedido: string;
68
+ pedido?: string;
69
69
  modo: string;
70
70
  tamanho: TamanhoResumoIa;
71
71
  analiseDrift: ContextPackPayload["analiseDrift"];
@@ -15,6 +15,8 @@ function normalizedTokens(value) {
15
15
  .filter((token) => token.length >= 4);
16
16
  }
17
17
  function selectRelevantTasks(tasks, request) {
18
+ if (request.startsWith("resumo:"))
19
+ return tasks.slice(0, 8);
18
20
  const requestTokens = new Set(normalizedTokens(request));
19
21
  const intentRoots = ["cancel", "notific", "export", "reagend", "registr", "consult"];
20
22
  const rootedMatches = intentRoots
@@ -115,6 +117,7 @@ export function selecionarContexto(payload, request) {
115
117
  };
116
118
  }
117
119
  export function criarContextPackAPartirDoResumo(input) {
120
+ const pedido = input.pedido?.trim() || `resumo:${input.tamanho}`;
118
121
  return selecionarContexto({
119
122
  geradoEm: input.resumo.geradoEm,
120
123
  arquivo: input.resumo.arquivo,
@@ -125,6 +128,6 @@ export function criarContextPackAPartirDoResumo(input) {
125
128
  guiaPorCapacidade: input.guiaPorCapacidade,
126
129
  texto: input.texto,
127
130
  resumo: input.resumo,
128
- }, input.pedido);
131
+ }, pedido);
129
132
  }
130
133
  //# sourceMappingURL=contextPack.js.map
@@ -18,6 +18,8 @@ export interface ResumoAlvoVerificacao {
18
18
  estrutura: EstruturaSaida;
19
19
  testesExecutados: boolean;
20
20
  origem?: "executado" | "cache";
21
+ tipoEvidencia?: "andaime_demonstrativo";
22
+ implementacaoExternaExecutada?: boolean;
21
23
  }
22
24
  export interface ResumoModuloVerificacao {
23
25
  modulo: string;
@@ -202,8 +202,8 @@ export async function comandoAjudaIa() {
202
202
  ]));
203
203
  console.log("");
204
204
  console.log(renderizarSecaoAscii("Capacidade de IA", [
205
- "fraca: `sema resumo <arquivo> --micro --drift none --json` usa stdout compacto sem inventar evidência de implementação",
206
- "média: `sema resumo <arquivo> --curto --drift none --json` + `sema drift <arquivo> --cache fresh --json`",
205
+ "fraca: `sema resumo <arquivo> --micro --drift none --json` já devolve o context-pack compacto sem inventar evidência de implementação",
206
+ "média: `sema resumo <arquivo> --curto --drift none --json` já compacta o resumo; use `sema drift <arquivo> --cache fresh --json` quando precisar de evidência do código",
207
207
  "forte: `sema contexto-ia <arquivo.sema> --saida <diretorio> --json` materializa o pacote completo",
208
208
  ]));
209
209
  console.log("");
@@ -214,7 +214,7 @@ export async function comandoAjudaIa() {
214
214
  "Use `sema interativo pipelines --json` para jogos, simulações e híbridos 3D, 2D, retro, texto, XR ou headless.",
215
215
  "Use `sema sync-codex` para regenerar o contexto governado do Codex na raiz.",
216
216
  "Use `sema instalar-exemplos` para materializar `exemplos/` oficiais sem sobrescrever arquivos locais.",
217
- "Use `sema resumo <arquivo> --micro --para onboarding --drift none --json` para IA fraca; o resumo contratual vem no stdout e campos derivados ficam não avaliados.",
217
+ "Use `sema resumo <arquivo> --micro --para onboarding --drift none --json` para IA fraca; o context-pack é automático e campos derivados ficam não avaliados.",
218
218
  "Se `sema resumo` ou outro gate estourar timeout local, aumente o timeout e tente de novo; timeout do agente nao e falha do Sema.",
219
219
  "Use `sema prompt-curto <arquivo> --curto --para mudanca` para colar contexto em modelo gratuito.",
220
220
  "Use `sema prompt-ia`, `sema prompt-ia-ui`, `sema prompt-ia-react` e `sema prompt-ia-sema-primeiro` conforme a tarefa.",
@@ -290,8 +290,8 @@ export async function comandoResumo(entrada, args, emJson) {
290
290
  const tamanho = normalizarTamanhoResumo(args);
291
291
  const modo = normalizarModoResumo(obterOpcao(args, "--para"));
292
292
  const pedido = obterOpcao(args, "--pedido");
293
- const pacoteSolicitado = possuiFlag(args, "--pacote");
294
293
  const somentePacote = possuiFlag(args, "--somente-pacote");
294
+ const pedidoExplícito = obterOpcao(args, "--pedido")?.trim();
295
295
  const pastaSaida = obterOpcao(args, "--saida");
296
296
  const escreverNaRaiz = possuiFlag(args, "--raiz");
297
297
  const alvo = entrada ? path.resolve(process.cwd(), entrada) : process.cwd();
@@ -304,22 +304,20 @@ export async function comandoResumo(entrada, args, emJson) {
304
304
  const texto = tamanho === "medio"
305
305
  ? renderizarResumoModuloMarkdown(resumoSemantico, modo, guiaPorCapacidade)
306
306
  : renderizarResumoModuloTexto(resumoSemantico, tamanho, modo);
307
- const contextPack = pacoteSolicitado && pedido
308
- ? criarContextPackAPartirDoResumo({
309
- resumo: resumoSemantico,
310
- pedido,
311
- modo,
312
- tamanho,
313
- analiseDrift: {
314
- modo: contexto.drift.modo,
315
- executada: contexto.drift.executada,
316
- sucesso: contexto.drift.sucesso,
317
- aviso: contexto.drift.aviso,
318
- },
319
- guiaPorCapacidade,
320
- texto,
321
- })
322
- : null;
307
+ const contextPack = criarContextPackAPartirDoResumo({
308
+ resumo: resumoSemantico,
309
+ pedido: pedidoExplícito,
310
+ modo,
311
+ tamanho,
312
+ analiseDrift: {
313
+ modo: contexto.drift.modo,
314
+ executada: contexto.drift.executada,
315
+ sucesso: contexto.drift.sucesso,
316
+ aviso: contexto.drift.aviso,
317
+ },
318
+ guiaPorCapacidade,
319
+ texto,
320
+ });
323
321
  let pastaResumo;
324
322
  let artefatosCompactos = [];
325
323
  if (pastaSaida) {
@@ -333,6 +331,10 @@ export async function comandoResumo(entrada, args, emJson) {
333
331
  console.log(JSON.stringify(contextPack, null, 2));
334
332
  return contexto.drift.sucesso === false ? 1 : 0;
335
333
  }
334
+ if (somentePacote) {
335
+ console.log(JSON.stringify(contextPack, null, 2));
336
+ return contexto.drift.sucesso === false ? 1 : 0;
337
+ }
336
338
  console.log(JSON.stringify({
337
339
  comando: "resumo",
338
340
  modo,
@@ -350,10 +352,7 @@ export async function comandoResumo(entrada, args, emJson) {
350
352
  avisos: analiseDrift.avisos,
351
353
  cache: contexto.drift.drift?.escopo_aplicado.cache ?? null,
352
354
  },
353
- guiaPorCapacidade,
354
- resumo: resumoSemantico,
355
- texto,
356
- ...(contextPack ? { contextPack } : {}),
355
+ contextPack,
357
356
  }, null, 2));
358
357
  return contexto.drift.sucesso === false ? 1 : 0;
359
358
  }
@@ -361,34 +360,22 @@ export async function comandoResumo(entrada, args, emJson) {
361
360
  console.log(`Resumo IA-first gerado em ${pastaResumo}`);
362
361
  console.log("");
363
362
  }
364
- if (contextPack) {
365
- console.log(JSON.stringify(contextPack, null, 2));
366
- return contexto.drift.sucesso === false ? 1 : 0;
367
- }
368
- console.log(texto);
363
+ console.log(JSON.stringify(contextPack, null, 2));
369
364
  return contexto.drift.sucesso === false ? 1 : 0;
370
365
  }
371
366
  const resumoProjeto = await gerarResumoProjetoIa(alvo, pastaSaida, escreverNaRaiz, analiseDrift);
372
- const arquivoResumo = tamanho === "micro"
373
- ? "SEMA_BRIEF.micro.txt"
374
- : tamanho === "curto"
375
- ? "SEMA_BRIEF.curto.txt"
376
- : "SEMA_BRIEF.md";
377
- const texto = await readFile(path.join(resumoProjeto.pastaSaida, arquivoResumo), "utf8");
378
367
  const indexTexto = await readFile(path.join(resumoProjeto.pastaSaida, "SEMA_INDEX.json"), "utf8").catch(() => "");
379
- const indexPack = pacoteSolicitado && pedido
380
- ? selecionarIndiceContexto({
381
- modulos: resumoProjeto.modulos,
382
- request: pedido,
383
- baseProject: resumoProjeto.baseProjeto,
384
- generatedAt: resumoProjeto.geradoEm,
385
- mode: modo,
386
- drift: resumoProjeto.analiseDrift.modo,
387
- sourceIndexChars: indexTexto.length,
388
- })
389
- : null;
368
+ const indexPack = selecionarIndiceContexto({
369
+ modulos: resumoProjeto.modulos,
370
+ request: pedidoExplícito ?? `resumo:${tamanho}`,
371
+ baseProject: resumoProjeto.baseProjeto,
372
+ generatedAt: resumoProjeto.geradoEm,
373
+ mode: modo,
374
+ drift: resumoProjeto.analiseDrift.modo,
375
+ sourceIndexChars: indexTexto.length,
376
+ });
390
377
  if (emJson) {
391
- if (somentePacote && indexPack) {
378
+ if (somentePacote) {
392
379
  console.log(JSON.stringify(indexPack, null, 2));
393
380
  return resumoProjeto.analiseDrift.sucesso === false ? 1 : 0;
394
381
  }
@@ -404,20 +391,13 @@ export async function comandoResumo(entrada, args, emJson) {
404
391
  ...resumoProjeto.analiseDrift,
405
392
  avisos: analiseDrift.avisos,
406
393
  },
407
- guiaPorCapacidade: resumoProjeto.guiaPorCapacidade,
408
- modulos: resumoProjeto.modulos,
409
- texto,
410
- ...(indexPack ? { indexPack } : {}),
394
+ indexPack,
411
395
  }, null, 2));
412
396
  return resumoProjeto.analiseDrift.sucesso === false ? 1 : 0;
413
397
  }
414
398
  console.log(`Resumo IA-first do projeto gerado em ${resumoProjeto.pastaSaida}`);
415
399
  console.log("");
416
- if (indexPack) {
417
- console.log(JSON.stringify(indexPack, null, 2));
418
- return resumoProjeto.analiseDrift.sucesso === false ? 1 : 0;
419
- }
420
- console.log(texto);
400
+ console.log(JSON.stringify(indexPack, null, 2));
421
401
  return resumoProjeto.analiseDrift.sucesso === false ? 1 : 0;
422
402
  }
423
403
  //# sourceMappingURL=index.part06.js.map
@@ -226,6 +226,11 @@ export async function comandoVerificar(entrada, baseSaida, cwd = process.cwd(),
226
226
  const alvos = opcoes.alvo
227
227
  ? [opcoes.alvo]
228
228
  : resolverAlvosVerificacao(contextoProjeto.configCarregada);
229
+ const testesNaoExecutaveis = diagnosticos.filter(item => item.codigo === "SEM125");
230
+ if (alvos.includes("python") && testesNaoExecutaveis.length > 0) {
231
+ console.error(formatarDiagnosticos(testesNaoExecutaveis));
232
+ return 1;
233
+ }
229
234
  const avisoEscopo = avisoEscopoStackVerificacao(contextoProjeto.configCarregada?.config.fontesLegado, alvos);
230
235
  if (avisoEscopo) {
231
236
  console.warn(avisoEscopo);
@@ -279,6 +284,7 @@ export async function comandoVerificar(entrada, baseSaida, cwd = process.cwd(),
279
284
  estrutura,
280
285
  testesExecutados: false,
281
286
  origem: "cache",
287
+ ...(alvo === "python" ? { tipoEvidencia: "andaime_demonstrativo", implementacaoExternaExecutada: false } : {}),
282
288
  });
283
289
  if (!emCache.sucesso) {
284
290
  imprimirResumoVerificacao([...resumos, resumoModulo]);
@@ -313,6 +319,7 @@ export async function comandoVerificar(entrada, baseSaida, cwd = process.cwd(),
313
319
  estrutura,
314
320
  testesExecutados,
315
321
  origem: "executado",
322
+ ...(alvo === "python" ? { tipoEvidencia: "andaime_demonstrativo", implementacaoExternaExecutada: false } : {}),
316
323
  });
317
324
  if (execucao.codigoSaida !== 0) {
318
325
  imprimirResumoVerificacao([...resumos, resumoModulo]);
@@ -347,6 +354,18 @@ export async function comandoVerificarJson(entrada, baseSaida, cwd = process.cwd
347
354
  const alvos = opcoes.alvo
348
355
  ? [opcoes.alvo]
349
356
  : resolverAlvosVerificacao(contextoProjeto.configCarregada);
357
+ const testesNaoExecutaveis = diagnosticos.filter(item => item.codigo === "SEM125");
358
+ if (alvos.includes("python") && testesNaoExecutaveis.length > 0) {
359
+ console.log(JSON.stringify({
360
+ comando: "verificar",
361
+ sucesso: false,
362
+ erro: "Casos de teste não executáveis em Python: substitua texto descritivo por entidades estruturadas.",
363
+ diagnosticos: testesNaoExecutaveis,
364
+ modulos: [],
365
+ totais: { modulos: 0, alvos: 0, arquivos: 0, testes: 0 },
366
+ }, null, 2));
367
+ return 1;
368
+ }
350
369
  const avisoEscopo = avisoEscopoStackVerificacao(contextoProjeto.configCarregada?.config.fontesLegado, alvos);
351
370
  const configuracoesAlvo = alvos.map((alvo) => ({
352
371
  alvo,
@@ -411,6 +430,7 @@ export async function comandoVerificarJson(entrada, baseSaida, cwd = process.cwd
411
430
  estrutura,
412
431
  testesExecutados: false,
413
432
  origem: "cache",
433
+ ...(alvo === "python" ? { tipoEvidencia: "andaime_demonstrativo", implementacaoExternaExecutada: false } : {}),
414
434
  });
415
435
  resumoModulo.saidaTestes.push({ alvo, stdout: "", stderr: `resultado reutilizado do cache de verificacao (chave ${chave.slice(0, 12)})` });
416
436
  if (!emCache.sucesso) {
@@ -444,6 +464,7 @@ export async function comandoVerificarJson(entrada, baseSaida, cwd = process.cwd
444
464
  estrutura,
445
465
  testesExecutados,
446
466
  origem: "executado",
467
+ ...(alvo === "python" ? { tipoEvidencia: "andaime_demonstrativo", implementacaoExternaExecutada: false } : {}),
447
468
  });
448
469
  resumoModulo.saidaTestes.push({ alvo, stdout: execucao.saidaPadrao, stderr: execucao.saidaErro });
449
470
  if (execucao.codigoSaida !== 0) {
@@ -2,7 +2,7 @@ import type { ResumoSemanticoModuloIa } from "./index.part01.js";
2
2
  type EntradaIndice = Pick<ResumoSemanticoModuloIa, "arquivo" | "modulo" | "faz" | "tarefasPrincipais" | "regrasCriticas" | "efeitos" | "riscosPrincipais" | "lacunas" | "arquivosProvaveis" | "checksSugeridos" | "testesMinimos" | "arquivosProvaveisEditar">;
3
3
  export type IndexPack = {
4
4
  schema: "sema.ai.index-pack/v1";
5
- selection: "deterministic_weighted_match";
5
+ selection: "deterministic_weighted_match" | "deterministic_summary_projection";
6
6
  request: string;
7
7
  source: {
8
8
  command: "resumo-projeto";
package/dist/indexPack.js CHANGED
@@ -17,7 +17,9 @@ function tokens(value) {
17
17
  function correspondeToken(campo, pedido) {
18
18
  return tokens(campo).some((token) => [...pedido].some((alvo) => token.startsWith(alvo) || alvo.startsWith(token)));
19
19
  }
20
- function selecionarValores(values, requestTokens, maximo) {
20
+ function selecionarValores(values, requestTokens, maximo, projetarResumoInteiro) {
21
+ if (projetarResumoInteiro)
22
+ return values.slice(0, maximo);
21
23
  return values
22
24
  .map((value, index) => ({
23
25
  value,
@@ -32,7 +34,7 @@ function selecionarValores(values, requestTokens, maximo) {
32
34
  function estimarTokens(chars) {
33
35
  return Math.ceil(chars / 4);
34
36
  }
35
- function compactarModulo(modulo, requestTokens) {
37
+ function compactarModulo(modulo, requestTokens, projetarResumoInteiro) {
36
38
  const tarefas = lista(modulo.tarefasPrincipais);
37
39
  const regras = lista(modulo.regrasCriticas);
38
40
  const efeitos = lista(modulo.efeitos);
@@ -52,36 +54,40 @@ function compactarModulo(modulo, requestTokens) {
52
54
  ["lacunas", lacunas, 1],
53
55
  ["arquivos", [...arquivos, ...arquivosEditar], 1],
54
56
  ];
55
- const relevanceScore = camposComPeso.reduce((total, [, values, weight]) => total + values.reduce((subtotal, value) => subtotal + (correspondeToken(value, requestTokens) ? weight : 0), 0), 0);
56
- if (relevanceScore === 0)
57
+ const relevanceScore = projetarResumoInteiro ? 1 : camposComPeso.reduce((total, [, values, weight]) => total + values.reduce((subtotal, value) => subtotal + (correspondeToken(value, requestTokens) ? weight : 0), 0), 0);
58
+ if (!projetarResumoInteiro && relevanceScore === 0)
57
59
  return null;
58
60
  return {
59
61
  module: texto(modulo.modulo),
60
62
  contract: texto(modulo.arquivo),
61
63
  relevanceScore,
62
64
  purpose: texto(modulo.faz),
63
- relevantTasks: selecionarValores(tarefas, requestTokens, 8),
64
- criticalRules: selecionarValores(regras, requestTokens, 8),
65
- effects: selecionarValores(efeitos, requestTokens, 6),
66
- risks: selecionarValores(riscos, requestTokens, 6),
67
- knownGaps: selecionarValores(lacunas, requestTokens, 6),
68
- likelyFiles: selecionarValores(arquivos, requestTokens, 8),
69
- filesToEdit: selecionarValores(arquivosEditar, requestTokens, 8),
70
- requiredChecks: selecionarValores(checks, requestTokens, 6),
71
- minimumTests: selecionarValores(testes, requestTokens, 6),
65
+ relevantTasks: selecionarValores(tarefas, requestTokens, 8, projetarResumoInteiro),
66
+ criticalRules: selecionarValores(regras, requestTokens, 8, projetarResumoInteiro),
67
+ effects: selecionarValores(efeitos, requestTokens, 6, projetarResumoInteiro),
68
+ risks: selecionarValores(riscos, requestTokens, 6, projetarResumoInteiro),
69
+ knownGaps: selecionarValores(lacunas, requestTokens, 6, projetarResumoInteiro),
70
+ likelyFiles: selecionarValores(arquivos, requestTokens, 8, projetarResumoInteiro),
71
+ filesToEdit: selecionarValores(arquivosEditar, requestTokens, 8, projetarResumoInteiro),
72
+ requiredChecks: selecionarValores(checks, requestTokens, 6, projetarResumoInteiro),
73
+ minimumTests: selecionarValores(testes, requestTokens, 6, projetarResumoInteiro),
72
74
  };
73
75
  }
74
76
  export function selecionarIndiceContexto(input) {
75
77
  const request = input.request.trim();
76
78
  const requestTokens = new Set(tokens(request));
79
+ const projetarResumoInteiro = request.length === 0 || request.startsWith("resumo:");
80
+ const selection = projetarResumoInteiro
81
+ ? "deterministic_summary_projection"
82
+ : "deterministic_weighted_match";
77
83
  const matches = input.modulos
78
- .map((modulo) => compactarModulo(modulo, requestTokens))
84
+ .map((modulo) => compactarModulo(modulo, requestTokens, projetarResumoInteiro))
79
85
  .filter((item) => item !== null)
80
86
  .sort((a, b) => b.relevanceScore - a.relevanceScore || a.module.localeCompare(b.module, "pt-BR"))
81
- .slice(0, 8);
87
+ .slice(0, projetarResumoInteiro ? input.modulos.length : 8);
82
88
  const base = {
83
89
  schema: "sema.ai.index-pack/v1",
84
- selection: "deterministic_weighted_match",
90
+ selection,
85
91
  request,
86
92
  source: {
87
93
  command: "resumo-projeto",
@@ -99,7 +105,7 @@ export function selecionarIndiceContexto(input) {
99
105
  omitted: [
100
106
  "SEMA_INDEX.json completo",
101
107
  "agentContextPack duplicado",
102
- "módulos sem correspondência literal ou estrutural no pedido",
108
+ "módulos e campos fora do orçamento da projeção compacta",
103
109
  "catálogo e arquivos não relacionados ao pedido",
104
110
  ],
105
111
  },
package/docs/testing.md CHANGED
@@ -148,3 +148,24 @@ Native generator evidence is mandatory when changing C#/.NET or C++ support.
148
148
  The `dotnet` smoke must compile and execute through the local .NET SDK. The
149
149
  `cpp` smoke must compile and execute through GCC, Clang, or MSVC; checking only
150
150
  for generated files is not sufficient.
151
+
152
+ Python verification must execute the generated code and inspect every declared
153
+ output expectation, including nested fields. A mismatching `expect` must fail;
154
+ the generator must never initialize an output from that expectation. Numeric
155
+ demonstration values respect comparison operators and simultaneous bounds.
156
+ Contradictory guarantees fail explicitly instead of being removed. Nested
157
+ paths preserve every segment and work with both JSON dictionaries and entities.
158
+
159
+ `SEM125` identifies descriptive scalar fixtures used where an entity or
160
+ structured type is declared. Contract analysis emits a compatibility warning
161
+ for historical declarative examples; executable Python generation rejects
162
+ them before running tests. Replace the scalar with a structured `given` block.
163
+ The regression suite is `testes/integracao/gerador-python-verificacao.test.ts`.
164
+
165
+ Generated Python functions are demonstration scaffolds. Their metadata declares
166
+ `origem_execucao: andaime_demonstrativo` and
167
+ `implementacao_externa_executada: false`. A linked `impl` is not executed by
168
+ this scaffold: its tests and guarantees cannot establish production behavior.
169
+ Use the application's real tests and drift evidence for that claim. Generated
170
+ failure cases currently retain the explicit synthetic error context and are
171
+ not evidence that the real implementation produces those errors.
@@ -0,0 +1,68 @@
1
+ module exemplos.python_verificacao {
2
+ docs {
3
+ resumo: "Regressao executavel Python para garantias numericas, formulario tipado, status e metadados aninhados."
4
+ }
5
+ entity FormularioEntrega {
6
+ fields {
7
+ tipo: Texto
8
+ }
9
+ }
10
+ entity MetaTemplate {
11
+ fields {
12
+ original_campaign_id: Texto
13
+ }
14
+ }
15
+ entity Template {
16
+ fields {
17
+ _template_meta: MetaTemplate
18
+ }
19
+ }
20
+ entity Status {
21
+ fields {
22
+ desired_action: Texto
23
+ }
24
+ }
25
+ task preparar {
26
+ input {
27
+ formulario: FormularioEntrega required
28
+ }
29
+ output {
30
+ poolSize: Inteiro
31
+ workersAplicados: Inteiro
32
+ template: Template
33
+ status: Status
34
+ }
35
+ rules {
36
+ formulario.tipo existe
37
+ }
38
+ guarantees {
39
+ poolSize > 0
40
+ workersAplicados >= 2
41
+ template._template_meta existe
42
+ template._template_meta.original_campaign_id em ["campanha_1"]
43
+ status.desired_action em ["iniciar", "parar"]
44
+ }
45
+ tests {
46
+ caso "verifica cada saida" {
47
+ given {
48
+ formulario {
49
+ tipo: "entrega"
50
+ }
51
+ }
52
+ expect {
53
+ sucesso: verdadeiro
54
+ poolSize: 1
55
+ workersAplicados: 2
56
+ template {
57
+ _template_meta {
58
+ original_campaign_id: "campanha_1"
59
+ }
60
+ }
61
+ status {
62
+ desired_action: "iniciar"
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,3 @@
1
+ import type { IrBlocoDeclarativo, IrTask } from "@sema/nucleo";
2
+ export declare function validarEntradasTestePython(bloco: IrBlocoDeclarativo, campos: Map<string, string>, tipos: Map<string, Map<string, string>>, contexto: string): void;
3
+ export declare function gerarExpectativasPython(task: IrTask, bloco: IrBlocoDeclarativo, tipos: Map<string, Map<string, string>>, prefixo?: string): string[];
@@ -0,0 +1,37 @@
1
+ import { formatarLiteralTestePython } from "./index.part01.js";
2
+ import { acessoPython } from "./valores.js";
3
+ export function validarEntradasTestePython(bloco, campos, tipos, contexto) {
4
+ for (const campo of bloco.campos) {
5
+ const tipo = campos.get(campo.nome);
6
+ if (tipo && tipos.has(tipo) && campo.tipo !== "nulo") {
7
+ throw new Error(`SEM125: ${contexto}.${campo.nome} exige ${tipo}; forneça bloco estruturado, não texto descritivo.`);
8
+ }
9
+ }
10
+ for (const filho of bloco.blocos) {
11
+ const tipo = campos.get(filho.nome);
12
+ if (tipo && tipos.has(tipo))
13
+ validarEntradasTestePython(filho.conteudo, tipos.get(tipo), tipos, `${contexto}.${filho.nome}`);
14
+ }
15
+ }
16
+ export function gerarExpectativasPython(task, bloco, tipos, prefixo = "") {
17
+ const linhas = [];
18
+ for (const campo of bloco.campos) {
19
+ const caminho = prefixo ? `${prefixo}.${campo.nome}` : campo.nome;
20
+ const raiz = caminho.split(".")[0];
21
+ const saida = task.output.find(item => item.nome === raiz);
22
+ if (!saida && caminho === "sucesso") {
23
+ linhas.push(` assert (resultado is not None) == ${formatarLiteralTestePython(campo.tipo, "Booleano")}, "Expectativa de sucesso divergente"`);
24
+ continue;
25
+ }
26
+ if (!saida)
27
+ throw new Error(`Expectativa "${caminho}" da task "${task.nome}" não corresponde a campo do output.`);
28
+ let tipo = saida.tipo;
29
+ for (const filho of caminho.split(".").slice(1))
30
+ tipo = tipos.get(tipo)?.get(filho) ?? "Json";
31
+ linhas.push(` assert ${acessoPython("resultado", caminho)} == ${formatarLiteralTestePython(campo.tipo, tipo)}, ${JSON.stringify(`Expectativa divergente: ${caminho}`)}`);
32
+ }
33
+ for (const filho of bloco.blocos)
34
+ linhas.push(...gerarExpectativasPython(task, filho.conteudo, tipos, prefixo ? `${prefixo}.${filho.nome}` : filho.nome));
35
+ return linhas;
36
+ }
37
+ //# sourceMappingURL=expectativas.js.map
@@ -25,4 +25,4 @@ export declare function gerarMapaLiteralPython(campos: Array<{
25
25
  export declare function coletarTiposCompostos(modulo: IrModulo): Map<string, Map<string, string>>;
26
26
  export declare function gerarLiteralBlocoTestePython(bloco: IrBlocoDeclarativo, tiposCompostos?: Map<string, Map<string, string>>, tiposDeclarados?: Map<string, string>, tipoAtual?: string): string;
27
27
  export declare function paraPascalCase(valor: string): string;
28
- export declare function gerarPreparacaoSaida(task: IrTask): string;
28
+ export declare function gerarPreparacaoSaida(task: IrTask, tipos?: Map<string, Map<string, string>>): string;
@@ -1,5 +1,6 @@
1
1
  // SEMA-GOVERNED: sema.geradores_codigo_governado
2
2
  // Descricao: modulo particionado; consulte contratos/sema/geradores_codigo_governado.sema antes de editar.
3
+ import { acessoPython, prepararSaidaPython } from "./valores.js";
3
4
  import { extrairTiposNomeados, mapearTipoParaPython, } from "@sema/padroes";
4
5
  export function mapearCampoParaPython(campo) {
5
6
  let anotacao;
@@ -79,7 +80,7 @@ export function gerarMapaErrosPython(erros) {
79
80
  return `{\n${entradas.map(([nome, mensagem]) => ` ${JSON.stringify(nome)}: ${JSON.stringify(mensagem)},`).join("\n")}\n}`;
80
81
  }
81
82
  export function formatarValorPython(valor, camposConhecidos, variavel) {
82
- const texto = valor.trim();
83
+ const texto = valor.trim().replace(/^-\s+(?=\d)/u, "-").replace(/^(-?\d+)\s*\.\s*(\d+)$/u, "$1.$2");
83
84
  if (/^-?\d+(?:\.\d+)?$/.test(texto)) {
84
85
  return texto;
85
86
  }
@@ -93,7 +94,7 @@ export function formatarValorPython(valor, camposConhecidos, variavel) {
93
94
  return "None";
94
95
  }
95
96
  if (camposConhecidos.has(texto.split(".")[0] ?? texto)) {
96
- return `${variavel}.${texto}`;
97
+ return acessoPython(variavel, texto);
97
98
  }
98
99
  return JSON.stringify(texto);
99
100
  }
@@ -106,23 +107,20 @@ export function extrairReferenciaCampoConhecido(valor, camposConhecidos) {
106
107
  return camposConhecidos.has(partes[0] ?? "") ? partes : null;
107
108
  }
108
109
  export function gerarInicializacaoReferenciaSaida(partes) {
109
- const [raiz, filho] = partes;
110
- if (!raiz || !filho) {
110
+ if (partes.length === 0)
111
111
  return [];
112
- }
113
112
  return [
114
- ` if saida.${raiz} is None:\n saida.${raiz} = SimpleNamespace()`,
115
- ` if getattr(saida.${raiz}, "${filho}", None) is None:\n saida.${raiz}.${filho} = "valor_garantido"`,
113
+ ` if ${acessoPython("saida", partes.join("."))} is None:\n sema_set(saida, ${JSON.stringify(partes)}, "valor_garantido")`,
116
114
  ];
117
115
  }
118
116
  export function resolverExpressaoPython(expressao, camposConhecidos, variavel) {
119
117
  switch (expressao.tipo) {
120
118
  case "existe":
121
- return `${variavel}.${expressao.alvo} is not None`;
119
+ return `${acessoPython(variavel, expressao.alvo)} is not None`;
122
120
  case "comparacao":
123
- return `${variavel}.${expressao.alvo} ${expressao.operador} ${formatarValorPython(expressao.valor, camposConhecidos, variavel)}`;
121
+ return `${acessoPython(variavel, expressao.alvo)} ${expressao.operador} ${formatarValorPython(expressao.valor, camposConhecidos, variavel)}`;
124
122
  case "pertencimento":
125
- return `${variavel}.${expressao.alvo} in [${(expressao.valores ?? []).map((valor) => formatarValorPython(valor, camposConhecidos, variavel)).join(", ")}]`;
123
+ return `${acessoPython(variavel, expressao.alvo)} in [${(expressao.valores ?? []).map((valor) => formatarValorPython(valor, camposConhecidos, variavel)).join(", ")}]`;
126
124
  case "predicado":
127
125
  return "True";
128
126
  case "composta":
@@ -253,7 +251,7 @@ export function resolverTipoItemTeste(tipoDeclarado) {
253
251
  return undefined;
254
252
  }
255
253
  export function formatarLiteralTestePython(valor, tipoDeclarado) {
256
- const bruto = valor.trim();
254
+ const bruto = valor.trim().replace(/^-\s+(?=\d)/u, "-").replace(/^(-?\d+)\s*\.\s*(\d+)$/u, "$1.$2");
257
255
  if (bruto.startsWith("[") && bruto.endsWith("]")) {
258
256
  const interior = bruto.slice(1, -1).trim();
259
257
  const tipoItem = resolverTipoItemTeste(tipoDeclarado);
@@ -348,36 +346,7 @@ export function paraPascalCase(valor) {
348
346
  .map((parte) => parte[0].toUpperCase() + parte.slice(1))
349
347
  .join("");
350
348
  }
351
- export function gerarPreparacaoSaida(task) {
352
- const camposSaida = new Set(task.output.map((campo) => campo.nome));
353
- const argumentos = task.output.map((campo) => `${campo.nome}=${valorPadraoPython(campo)}`).join(", ");
354
- const ajustes = [];
355
- for (const garantia of task.garantiasEstruturadas) {
356
- if (garantia.tipo === "pertencimento" && garantia.valores && camposSaida.has(garantia.alvo)) {
357
- ajustes.push(` saida.${garantia.alvo} = ${formatarValorPython(garantia.valores[0] ?? "", camposSaida, "saida")}`);
358
- }
359
- if (garantia.tipo === "comparacao" && garantia.valor && camposSaida.has(garantia.alvo.split(".")[0] ?? garantia.alvo) && !garantia.alvo.includes(".")) {
360
- ajustes.push(` saida.${garantia.alvo} = ${formatarValorPython(garantia.valor, camposSaida, "saida")}`);
361
- }
362
- if (garantia.tipo === "comparacao" && garantia.valor && garantia.alvo.includes(".")) {
363
- const [raiz, filho] = garantia.alvo.split(".", 2);
364
- if (raiz && filho && camposSaida.has(raiz)) {
365
- const referenciaValor = extrairReferenciaCampoConhecido(garantia.valor, camposSaida);
366
- if (referenciaValor) {
367
- ajustes.push(...gerarInicializacaoReferenciaSaida(referenciaValor));
368
- }
369
- ajustes.push(` if saida.${raiz} is None:\n saida.${raiz} = SimpleNamespace()`);
370
- ajustes.push(` saida.${raiz}.${filho} = ${formatarValorPython(garantia.valor, camposSaida, "saida")}`);
371
- }
372
- }
373
- if (garantia.tipo === "existe" && garantia.alvo.includes(".")) {
374
- const [raiz, filho] = garantia.alvo.split(".", 2);
375
- if (raiz && filho && camposSaida.has(raiz)) {
376
- ajustes.push(` if saida.${raiz} is None:\n saida.${raiz} = SimpleNamespace()`);
377
- ajustes.push(` if getattr(saida.${raiz}, "${filho}", None) is None:\n saida.${raiz}.${filho} = "valor_garantido"`);
378
- }
379
- }
380
- }
381
- return ` saida = ${task.nome}Saida(${argumentos})\n${ajustes.join("\n")}`;
349
+ export function gerarPreparacaoSaida(task, tipos = new Map()) {
350
+ return prepararSaidaPython(task, tipos);
382
351
  }
383
352
  //# sourceMappingURL=index.part01.js.map
@@ -1,8 +1,11 @@
1
1
  // SEMA-GOVERNED: sema.geradores_codigo_governado
2
2
  // Descricao: modulo particionado; consulte contratos/sema/geradores_codigo_governado.sema antes de editar.
3
3
  import path from "node:path";
4
+ import { prepararSaidaPython } from "./valores.js";
5
+ import { RUNTIME_PYTHON } from "./runtime.js";
6
+ import { gerarExpectativasPython, validarEntradasTestePython } from "./expectativas.js";
4
7
  import { descreverEstruturaModulo, normalizarNomeModulo, normalizarNomeParaSimbolo, } from "@sema/padroes";
5
- import { coletarTiposCompostos, coletarTiposExternos, formatarLiteralTestePython, gerarComentarioInvariantesPython, gerarDataclass, gerarListaCamposPython, gerarLiteralBlocoTestePython, gerarMapaErrosPython, gerarPreparacaoSaida, mapearCampoParaPython, paraPascalCase, resolverExpressaoPython } from "./index.part01.js";
8
+ import { coletarTiposCompostos, coletarTiposExternos, formatarLiteralTestePython, gerarComentarioInvariantesPython, gerarDataclass, gerarListaCamposPython, gerarLiteralBlocoTestePython, gerarMapaErrosPython, mapearCampoParaPython, paraPascalCase, resolverExpressaoPython } from "./index.part01.js";
6
9
  export function finalizarBlocoPython(linhas) {
7
10
  if (linhas.length === 0) {
8
11
  return " pass";
@@ -48,6 +51,8 @@ export function gerarMetadadosTask(task) {
48
51
  "impl": ${implementacoes},
49
52
  "errors": ${gerarMapaErrosPython(task.errors)},
50
53
  "guarantees": ${JSON.stringify(task.guarantees, null, 2)},
54
+ "origem_execucao": "andaime_demonstrativo",
55
+ "implementacao_externa_executada": False,
51
56
  }
52
57
  `;
53
58
  }
@@ -191,7 +196,7 @@ ${erroAutorizacao ? ` if contexto_execucao["erro_esperado"] == "${erroAutoriz
191
196
  ${task.stateContract ? ` # Vinculo de estado: ${task.stateContract.nomeEstado ?? "nao_definido"}\n # Transicoes declaradas pela task: ${task.stateContract.transicoes.map((transicao) => `${transicao.origem}->${transicao.destino}`).join(", ") || "nenhuma"}` : ""}
192
197
  ${implementacoes}
193
198
  ${efeitos}
194
- ${gerarPreparacaoSaida(task)}
199
+ ${prepararSaidaPython(task, tiposCompostos)}
195
200
  ${garantias}
196
201
  `;
197
202
  }
@@ -202,6 +207,7 @@ export function gerarTestes(modulo) {
202
207
  const nomeFuncao = `executar_${normalizarNomeParaSimbolo(task.nome)}`;
203
208
  const tiposEntrada = new Map(task.input.map((campo) => [campo.nome, campo.tipo]));
204
209
  for (const caso of task.tests) {
210
+ validarEntradasTestePython(caso.given, tiposEntrada, tiposCompostos, `given da task ${task.nome}, caso ${caso.nome}`);
205
211
  const argumentos = [
206
212
  ...caso.given.campos
207
213
  .filter((campo) => tiposEntrada.has(campo.nome))
@@ -222,14 +228,15 @@ export function gerarTestes(modulo) {
222
228
  linhas.push(`def test_${normalizarNomeParaSimbolo(task.nome)}_${normalizarNomeParaSimbolo(caso.nome)}() -> None:\n entrada = ${task.nome}Entrada(${argumentos})\n contexto = { ${contextoLinhas.join(", ")} }\n with pytest.raises(${task.nome}_${tipoErro}Erro):\n ${nomeFuncao}(entrada, contexto)\n`);
223
229
  continue;
224
230
  }
225
- linhas.push(`def test_${normalizarNomeParaSimbolo(task.nome)}_${normalizarNomeParaSimbolo(caso.nome)}() -> None:\n entrada = ${task.nome}Entrada(${argumentos})\n resultado = ${nomeFuncao}(entrada)\n assert resultado is not None\n`);
231
+ const expectativas = gerarExpectativasPython(task, caso.expect, tiposCompostos);
232
+ linhas.push(`def test_${normalizarNomeParaSimbolo(task.nome)}_${normalizarNomeParaSimbolo(caso.nome)}() -> None:\n entrada = ${task.nome}Entrada(${argumentos})\n resultado = ${nomeFuncao}(entrada)\n${expectativas.length ? expectativas.join("\n") : " assert resultado is not None"}\n`);
226
233
  }
227
234
  }
228
235
  return linhas.join("\n");
229
236
  }
230
237
  export function gerarCabecalhoSemaPython(modulo, tipo = "contrato") {
231
238
  const descricoes = {
232
- contrato: "artefato Python gerado para executar e revisar as regras declaradas no contrato Sema.",
239
+ contrato: "andaime Python demonstrativo para revisar regras; não constitui prova de execução da implementação externa.",
233
240
  teste: "testes Python gerados a partir dos casos do contrato Sema.",
234
241
  schemas: "schemas FastAPI derivados do contrato Sema para entrada e saída públicas.",
235
242
  service: "service FastAPI que conecta o scaffold do framework às tasks governadas pelo contrato Sema.",
@@ -262,7 +269,7 @@ export function gerarPythonBase(modulo) {
262
269
  const tiposCompostos = coletarTiposCompostos(modulo);
263
270
  const tasks = modulo.tasks.map((task) => gerarTask(task, tiposCompostos)).join("\n");
264
271
  const contratosPublicos = gerarRotas(modulo);
265
- const codigo = `${gerarCabecalhoSemaPython(modulo)}from __future__ import annotations\n${interoperabilidades ? `${interoperabilidades}\n` : ""}\nfrom dataclasses import dataclass\nfrom types import SimpleNamespace\n\n${tiposExternos}\n${tipos}\n${enums}\n${entidades}\n${states}\n${flows}\n${routes}\n${tasks}\n${contratosPublicos}\n`;
272
+ const codigo = `${gerarCabecalhoSemaPython(modulo)}from __future__ import annotations\n${interoperabilidades ? `${interoperabilidades}\n` : ""}\nfrom dataclasses import dataclass\nfrom types import SimpleNamespace\n${RUNTIME_PYTHON}\n${tiposExternos}\n${tipos}\n${enums}\n${entidades}\n${states}\n${flows}\n${routes}\n${tasks}\n${contratosPublicos}\n`;
266
273
  const testes = `${gerarCabecalhoSemaPython(modulo, "teste")}${gerarTestes(modulo)}`;
267
274
  return [
268
275
  { caminhoRelativo: `${nomeBase}.py`, conteudo: codigo },
@@ -0,0 +1 @@
1
+ export declare const RUNTIME_PYTHON = "\nimport math\n\ndef sema_get(objeto, caminho):\n for campo in caminho:\n if objeto is None:\n return None\n objeto = objeto.get(campo) if isinstance(objeto, dict) else getattr(objeto, campo, None)\n return objeto\n\ndef sema_set(objeto, caminho, valor):\n for campo in caminho[:-1]:\n filho = sema_get(objeto, [campo])\n if filho is None:\n filho = {} if isinstance(objeto, dict) else SimpleNamespace()\n if isinstance(objeto, dict):\n objeto[campo] = filho\n else:\n setattr(objeto, campo, filho)\n if not isinstance(filho, dict) and not hasattr(filho, \"__dict__\"):\n raise ValueError(f\"Caminho de objeto incompat\u00EDvel: {campo}\")\n objeto = filho\n if isinstance(objeto, dict):\n objeto[caminho[-1]] = valor\n else:\n setattr(objeto, caminho[-1], valor)\n\ndef sema_sintetizar(atual, restricoes, inteiro=False):\n # Apenas valores demonstrativos: expect nunca participa desta s\u00EDntese.\n def aceita(valor):\n for operador, limite in restricoes:\n if operador == \"existe\" and valor is None:\n return False\n if operador == \"in\" and valor not in limite:\n return False\n try:\n if operador == \"==\" and valor != limite: return False\n if operador == \"!=\" and valor == limite: return False\n if operador == \">\" and not valor > limite: return False\n if operador == \">=\" and not valor >= limite: return False\n if operador == \"<\" and not valor < limite: return False\n if operador == \"<=\" and not valor <= limite: return False\n except TypeError:\n return False\n return True\n\n candidatos = [atual]\n numeros = []\n for operador, limite in restricoes:\n if operador == \"in\":\n candidatos.extend(limite)\n elif operador != \"existe\":\n candidatos.append(limite)\n if isinstance(limite, (int, float)) and not isinstance(limite, bool):\n numeros.append(limite)\n candidatos.extend([limite - 1, limite + 1, math.floor(limite), math.ceil(limite)])\n numeros.sort()\n candidatos.extend((a + b) / 2 for a, b in zip(numeros, numeros[1:]))\n candidatos.extend([1, 0, -1, True, False, \"valor_garantido\", \"outro_valor\"])\n for candidato in candidatos:\n if inteiro and (not isinstance(candidato, int) or isinstance(candidato, bool)):\n continue\n if aceita(candidato):\n return candidato\n raise ValueError(\"N\u00E3o foi poss\u00EDvel sintetizar sa\u00EDda demonstrativa compat\u00EDvel com as garantias; forne\u00E7a implementa\u00E7\u00E3o real.\")\n";
@@ -0,0 +1,70 @@
1
+ // SEMA-GOVERNED: sema.produto.gerador_python_verificacao
2
+ // Contrato: contratos/sema/gerador_python_verificacao.sema
3
+ // Descrição: acesso uniforme a mapas e entidades e síntese demonstrativa de garantias.
4
+ export const RUNTIME_PYTHON = `
5
+ import math
6
+
7
+ def sema_get(objeto, caminho):
8
+ for campo in caminho:
9
+ if objeto is None:
10
+ return None
11
+ objeto = objeto.get(campo) if isinstance(objeto, dict) else getattr(objeto, campo, None)
12
+ return objeto
13
+
14
+ def sema_set(objeto, caminho, valor):
15
+ for campo in caminho[:-1]:
16
+ filho = sema_get(objeto, [campo])
17
+ if filho is None:
18
+ filho = {} if isinstance(objeto, dict) else SimpleNamespace()
19
+ if isinstance(objeto, dict):
20
+ objeto[campo] = filho
21
+ else:
22
+ setattr(objeto, campo, filho)
23
+ if not isinstance(filho, dict) and not hasattr(filho, "__dict__"):
24
+ raise ValueError(f"Caminho de objeto incompatível: {campo}")
25
+ objeto = filho
26
+ if isinstance(objeto, dict):
27
+ objeto[caminho[-1]] = valor
28
+ else:
29
+ setattr(objeto, caminho[-1], valor)
30
+
31
+ def sema_sintetizar(atual, restricoes, inteiro=False):
32
+ # Apenas valores demonstrativos: expect nunca participa desta síntese.
33
+ def aceita(valor):
34
+ for operador, limite in restricoes:
35
+ if operador == "existe" and valor is None:
36
+ return False
37
+ if operador == "in" and valor not in limite:
38
+ return False
39
+ try:
40
+ if operador == "==" and valor != limite: return False
41
+ if operador == "!=" and valor == limite: return False
42
+ if operador == ">" and not valor > limite: return False
43
+ if operador == ">=" and not valor >= limite: return False
44
+ if operador == "<" and not valor < limite: return False
45
+ if operador == "<=" and not valor <= limite: return False
46
+ except TypeError:
47
+ return False
48
+ return True
49
+
50
+ candidatos = [atual]
51
+ numeros = []
52
+ for operador, limite in restricoes:
53
+ if operador == "in":
54
+ candidatos.extend(limite)
55
+ elif operador != "existe":
56
+ candidatos.append(limite)
57
+ if isinstance(limite, (int, float)) and not isinstance(limite, bool):
58
+ numeros.append(limite)
59
+ candidatos.extend([limite - 1, limite + 1, math.floor(limite), math.ceil(limite)])
60
+ numeros.sort()
61
+ candidatos.extend((a + b) / 2 for a, b in zip(numeros, numeros[1:]))
62
+ candidatos.extend([1, 0, -1, True, False, "valor_garantido", "outro_valor"])
63
+ for candidato in candidatos:
64
+ if inteiro and (not isinstance(candidato, int) or isinstance(candidato, bool)):
65
+ continue
66
+ if aceita(candidato):
67
+ return candidato
68
+ raise ValueError("Não foi possível sintetizar saída demonstrativa compatível com as garantias; forneça implementação real.")
69
+ `;
70
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1,4 @@
1
+ import type { IrTask } from "@sema/nucleo";
2
+ export declare function acessoPython(variavel: string, caminho: string): string;
3
+ export declare function valorCompostoPython(tipo: string, tipos: Map<string, Map<string, string>>, vistos?: Set<string>): string;
4
+ export declare function prepararSaidaPython(task: IrTask, tipos: Map<string, Map<string, string>>): string;
@@ -0,0 +1,72 @@
1
+ import { formatarValorPython } from "./index.part01.js";
2
+ export function acessoPython(variavel, caminho) {
3
+ return `sema_get(${variavel}, ${JSON.stringify(caminho.split("."))})`;
4
+ }
5
+ export function valorCompostoPython(tipo, tipos, vistos = new Set()) {
6
+ if (/^(Lista|Mapa)</u.test(tipo) || /\[\]$/u.test(tipo))
7
+ return tipo.startsWith("Mapa") ? "{}" : "[]";
8
+ if (tipo.startsWith("Opcional<") || tipo.endsWith("?"))
9
+ return "None";
10
+ if (["Numero", "Inteiro", "Decimal"].includes(tipo))
11
+ return "1";
12
+ if (tipo === "Booleano")
13
+ return "False";
14
+ if (["Json", "Objeto"].includes(tipo))
15
+ return "{}";
16
+ if (!tipos.has(tipo))
17
+ return ["Texto", "Id", "Email", "Url", "Data", "DataHora", "Timestamp"].includes(tipo) ? '"valor_garantido"' : "SimpleNamespace()";
18
+ if (vistos.has(tipo))
19
+ return "None";
20
+ const proximos = new Set(vistos).add(tipo);
21
+ return `${tipo}(${[...tipos.get(tipo)].map(([nome, filho]) => `${nome}=${valorCompostoPython(filho, tipos, proximos)}`).join(", ")})`;
22
+ }
23
+ function termosConjuntos(expressao) {
24
+ return expressao.tipo === "composta" && expressao.operadorLogico === "e"
25
+ ? expressao.termos.flatMap(termosConjuntos) : [expressao];
26
+ }
27
+ export function prepararSaidaPython(task, tipos) {
28
+ const campos = new Set(task.output.map(campo => campo.nome));
29
+ const grupos = new Map();
30
+ for (const regra of task.garantiasEstruturadas.flatMap(termosConjuntos)) {
31
+ if (!("alvo" in regra) || !campos.has(regra.alvo.split(".")[0]))
32
+ continue;
33
+ if (!["comparacao", "pertencimento", "existe"].includes(regra.tipo))
34
+ continue;
35
+ grupos.set(regra.alvo, [...(grupos.get(regra.alvo) ?? []), regra]);
36
+ }
37
+ const argumentos = task.output.map(campo => `${campo.nome}=${campo.opcional ? "None" : valorCompostoPython(campo.tipo, tipos)}`);
38
+ const linhas = [` # Andaime demonstrativo; não executa a implementação externa vinculada.`, ` saida = ${task.nome}Saida(${argumentos.join(", ")})`];
39
+ // Inicialize folhas antes de suas referências e preserve nós que são pais.
40
+ const referencias = new Set();
41
+ for (const regras of grupos.values())
42
+ for (const regra of regras) {
43
+ if (regra.tipo === "comparacao" && campos.has(regra.valor.split(".")[0]))
44
+ referencias.add(regra.valor);
45
+ }
46
+ for (const referencia of referencias) {
47
+ if (!grupos.has(referencia))
48
+ grupos.set(referencia, [{ tipo: "existe", alvo: referencia, textoOriginal: `${referencia} existe` }]);
49
+ }
50
+ const caminhos = [...grupos.keys()].sort((a, b) => Number(referencias.has(b)) - Number(referencias.has(a)) || a.split(".").length - b.split(".").length);
51
+ for (const caminho of caminhos) {
52
+ const regras = grupos.get(caminho);
53
+ let tipo = task.output.find(campo => campo.nome === caminho.split(".")[0])?.tipo ?? "Json";
54
+ for (const segmento of caminho.split(".").slice(1))
55
+ tipo = tipos.get(tipo)?.get(segmento) ?? "Json";
56
+ const restricoes = regras.map(regra => {
57
+ if (regra.tipo === "comparacao")
58
+ return `(${JSON.stringify(regra.operador)}, ${formatarValorPython(regra.valor, campos, "saida")})`;
59
+ if (regra.tipo === "pertencimento")
60
+ return `("in", [${(regra.valores ?? []).map(valor => formatarValorPython(valor, campos, "saida")).join(", ")}])`;
61
+ return '("existe", None)';
62
+ });
63
+ if (regras.every(regra => regra.tipo === "existe") && caminhos.some(outro => outro.startsWith(`${caminho}.`))) {
64
+ linhas.push(` if ${acessoPython("saida", caminho)} is None:`, ` sema_set(saida, ${JSON.stringify(caminho.split("."))}, ${valorCompostoPython(tipo, tipos) === "None" ? "SimpleNamespace()" : valorCompostoPython(tipo, tipos)})`);
65
+ }
66
+ else {
67
+ linhas.push(` sema_set(saida, ${JSON.stringify(caminho.split("."))}, sema_sintetizar(${acessoPython("saida", caminho)}, [${restricoes.join(", ")}], inteiro=${tipo === "Inteiro" ? "True" : "False"}))`);
68
+ }
69
+ }
70
+ return linhas.join("\n");
71
+ }
72
+ //# sourceMappingURL=valores.js.map
@@ -8,6 +8,7 @@ import { validarFlow, validarInvariantesDeCampos, validarState } from "./analisa
8
8
  import { validarStatusTextoComState, validarTask } from "./analisador.part07.js";
9
9
  import { validarGuardrailsSeguranca, validarRoute } from "./analisador.part06.js";
10
10
  import { validarDatabase } from "./analisador.part02.js";
11
+ import { validarTiposEstruturadosTestes } from "./testesTipos.js";
11
12
  export function analisarSemantica(modulo, opcoes = {}) {
12
13
  const diagnosticos = [];
13
14
  const simbolos = new Map();
@@ -177,6 +178,7 @@ export function analisarSemantica(modulo, opcoes = {}) {
177
178
  for (const task of modulo.tasks) {
178
179
  validarTask(task, tiposConhecidos, statesConhecidos, diagnosticos);
179
180
  }
181
+ validarTiposEstruturadosTestes(modulo, diagnosticos);
180
182
  for (const flow of modulo.flows) {
181
183
  validarFlow(flow, tasksConhecidas, tarefasDetalhadas, diagnosticos);
182
184
  }
@@ -0,0 +1,3 @@
1
+ import type { ModuloAst } from "../ast/tipos.js";
2
+ import { type Diagnostico } from "../diagnosticos/index.js";
3
+ export declare function validarTiposEstruturadosTestes(modulo: ModuloAst, diagnosticos: Diagnostico[]): void;
@@ -0,0 +1,35 @@
1
+ import { criarDiagnostico } from "../diagnosticos/index.js";
2
+ export function validarTiposEstruturadosTestes(modulo, diagnosticos) {
3
+ const tipos = new Map();
4
+ for (const item of [...modulo.types, ...modulo.entities]) {
5
+ const fields = item.corpo.blocos.find(bloco => bloco.tipo === "bloco_generico" && bloco.palavraChave === "fields");
6
+ tipos.set(item.nome, fields?.tipo === "bloco_generico" ? fields.campos : item.corpo.campos);
7
+ }
8
+ function conferir(bloco, campos, contexto) {
9
+ for (const valor of bloco.campos) {
10
+ const declarado = campos.find(campo => campo.nome === valor.nome);
11
+ if (!declarado || !tipos.has(declarado.valor))
12
+ continue;
13
+ if (valor.valor === "nulo" && !declarado.modificadores.includes("required"))
14
+ continue;
15
+ diagnosticos.push(criarDiagnostico("SEM125", `${contexto}: "${valor.nome}" exige ${declarado.valor}, mas recebeu literal escalar; caso não executável.`, "aviso", valor.intervalo, `Use ${valor.nome} { ... } com os campos de ${declarado.valor}; texto descritivo não é uma entidade.`));
16
+ }
17
+ for (const filho of bloco.blocos) {
18
+ if (filho.tipo !== "bloco_generico")
19
+ continue;
20
+ const tipo = campos.find(campo => campo.nome === filho.nome)?.valor;
21
+ if (tipo && tipos.has(tipo))
22
+ conferir(filho, tipos.get(tipo), `${contexto}.${filho.nome}`);
23
+ }
24
+ }
25
+ for (const task of modulo.tasks)
26
+ for (const caso of task.tests?.blocos ?? []) {
27
+ if (caso.tipo !== "caso_teste")
28
+ continue;
29
+ if (caso.given)
30
+ conferir(caso.given, task.input?.campos ?? [], `given do caso "${caso.nome}" da task "${task.nome}"`);
31
+ if (caso.expect)
32
+ conferir(caso.expect, task.output?.campos ?? [], `expect do caso "${caso.nome}" da task "${task.nome}"`);
33
+ }
34
+ }
35
+ //# sourceMappingURL=testesTipos.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@semacode/cli",
3
- "version": "4.0.0",
3
+ "version": "4.1.1",
4
4
  "description": "Local-first semantic governance CLI for AI agents, proven with Claude, Codex, zCode (GLM) and Kimi: contracts, drift, impact, docs gates, and code generation.",
5
5
  "type": "module",
6
6
  "icon": "logo.png",