@semacode/cli 4.1.3 → 4.2.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.
@@ -25,7 +25,7 @@ export function criarAjudaRaiz(versao) {
25
25
  "",
26
26
  renderizarSecaoAscii("Fluxos rápidos", [
27
27
  "[1] Projeto novo / produção inicial",
28
- "sema iniciar --template <base|nestjs|fastapi|nextjs-api|nextjs-consumer|react-vite-consumer|angular-consumer|flutter-consumer|node-firebase-worker|aspnet-api|springboot-api|go-http-api|rust-axum-api|cpp-service-bridge> [--force] [--com-exemplos]",
28
+ "sema iniciar --template <base|nestjs|fastapi|nextjs-api|nextjs-consumer|react-vite-consumer|angular-consumer|flutter-consumer|node-firebase-worker|aspnet-api|springboot-api|go-http-api|rust-axum-api|cpp-service-bridge> [--janela-contexto <tokens>] [--orcamento-contexto <tokens>] [--reserva-saida <tokens>] [--force] [--com-exemplos]",
29
29
  "Arquivos existentes são preservados; use --force somente para sobrescrita explícita. O starter de exemplos vem por padrão; --com-exemplos instala o pacote completo. Em subpasta de workspace já governado, a subpasta é registrada nos diretoriosCodigo do sema.config.json ancestral em vez de criar config local.",
30
30
  "sema validar contratos/<modulo>.sema --json",
31
31
  "sema compilar <arquivo-ou-pasta> --alvo <typescript|python|php|dart|lua|javascript|html|css|dotnet|cpp> --saida <diretorio>",
@@ -145,7 +145,7 @@ export function criarAjudaRaiz(versao) {
145
145
  "sema contexto-ia <arquivo.sema> [--saida <diretorio>] [--capacidade <fraca|media|forte>] [--pacote] [--json]",
146
146
  "sema sync-codex [--json]",
147
147
  "sema instalar-exemplos [--json]",
148
- "sema sessao [intencao] [entrada] [--json]",
148
+ "sema sessao [intencao] [entrada] [--arquivo <contrato-ou-codigo>] [--janela-contexto <tokens>] [--orcamento-contexto <tokens>] [--reserva-saida <tokens>] [--salvar-perfil] [--json]",
149
149
  "sema docs-impacto --intencao <acao> [--arquivo <caminho>] [--criar-ausentes] [--completo] [--json]",
150
150
  "sema finalizar-mudanca --intencao <acao> [--arquivo <caminho>] [--doc-lida <caminho>] [--json]",
151
151
  "sema author briefing <arquivo.sema> [--json]",
@@ -1,34 +1 @@
1
- export interface SessaoEnvelope {
2
- comando: "sessao";
3
- sucesso: boolean;
4
- workspace: {
5
- baseProjeto: string;
6
- nomePasta: string;
7
- hashContratos: string;
8
- cliVersao: string;
9
- };
10
- frescor: {
11
- fresco: boolean;
12
- hashArtefato: string | null;
13
- hashAtual: string;
14
- sugestao: string;
15
- };
16
- contratos: {
17
- total: number;
18
- modulos: string[];
19
- };
20
- intencoes: {
21
- intencao: string;
22
- categorias: string[];
23
- docsBloqueantes: string[];
24
- docsRecomendadas: string[];
25
- };
26
- gates: {
27
- drift: string;
28
- validar: string;
29
- verificar: string;
30
- finalizar: string;
31
- };
32
- proximosPassos: string[];
33
- }
34
1
  export declare function comandoSessao(posicionais: string[], args: string[], emJson: boolean, cwd?: string): Promise<number>;
@@ -1,86 +1,87 @@
1
1
  // SEMA-GOVERNED: sema.produto.cli_sessao
2
- // Descricao: comando sessao devolve envelope compacto de abertura governada.
2
+ // Descrição: abertura progressiva com perfil explícito, frescor e navegação limitada por orçamento.
3
3
  import path from "node:path";
4
4
  import { verificarFrescorArtefatos } from "./index.part04.js";
5
5
  import { carregarProjeto } from "./projetoCarregar.js";
6
+ import { carregarConfiguracaoProjeto } from "./projetoConfig.js";
6
7
  import { resolverDocumentacaoObrigatoria } from "./docs.js";
8
+ import { resolverPerfilContexto, salvarPerfilContexto, estimarRespostaTokens, PERGUNTA_CONTEXTO } from "./contextBudget.js";
9
+ import { navegarContextoSessao } from "./sessionContext.js";
10
+ function opcao(args, nome) {
11
+ const i = args.findIndex((arg) => arg === nome || arg.startsWith(`${nome}=`));
12
+ return i < 0 ? undefined : args[i].startsWith(`${nome}=`) ? args[i].slice(nome.length + 1) : args[i + 1];
13
+ }
7
14
  export async function comandoSessao(posicionais, args, emJson, cwd = process.cwd()) {
8
- const intencao = posicionais[0] ?? args.find((arg) => arg.startsWith("--intencao="))?.split("=")[1] ?? "";
15
+ const emitir = (payload, codigo = 0) => {
16
+ if (emJson)
17
+ console.log(JSON.stringify(payload));
18
+ else {
19
+ const p = payload;
20
+ console.log(`Sessão Sema — ${p.status ?? "consulta"}`);
21
+ for (const candidato of p.contexto?.candidatos ?? [])
22
+ console.log(` ${candidato.contrato} — ${candidato.motivo}`);
23
+ for (const passo of p.proximosPassos ?? [])
24
+ console.log(` → ${passo}`);
25
+ }
26
+ return codigo;
27
+ };
28
+ const intencao = posicionais[0] ?? "";
29
+ if (intencao.length > 2048)
30
+ return emitir({ comando: "sessao", sucesso: false, status: "intencao_longa", proximosPassos: ["Descreva a intenção em até 2048 caracteres e informe --arquivo para fixar o escopo."] }, 1);
9
31
  const entrada = posicionais[1];
10
- const baseProjeto = entrada ? path.resolve(cwd, entrada) : cwd;
11
- const contextoProjeto = await carregarProjeto(entrada, cwd, {
12
- escopo: "projeto",
13
- adiarDescobertaCodigo: true,
14
- });
15
- const pacoteCli = (await import("../package.json", { with: { type: "json" } })).default;
16
- const frescor = await verificarFrescorArtefatos(contextoProjeto.baseProjeto);
17
- const modulosNomes = contextoProjeto.modulosSelecionados.map((item) => item.resultado.modulo?.nome ?? path.basename(item.caminho, ".sema"));
18
- let categorias = [];
19
- let docsBloqueantes = [];
20
- let docsRecomendadas = [];
21
- if (intencao.trim()) {
22
- const impacto = await resolverDocumentacaoObrigatoria({
23
- baseProjeto: contextoProjeto.baseProjeto,
24
- intencao,
25
- arquivosAlvo: [],
26
- criarAusentes: false,
32
+ const configuracao = await carregarConfiguracaoProjeto(entrada ? path.resolve(cwd, entrada) : cwd);
33
+ const base = configuracao?.baseDiretorio ?? cwd;
34
+ const perfil = await resolverPerfilContexto(base, args);
35
+ if (!perfil)
36
+ return emitir({
37
+ comando: "sessao", sucesso: true, status: "configuracao_necessaria", execucaoAutorizada: false,
38
+ proximosPassos: [PERGUNTA_CONTEXTO, 'Repita sema sessao "<intenção>" --janela-contexto <tokens> --salvar-perfil --json. Não infira a janela pelo modelo.'],
27
39
  });
28
- categorias = impacto.categorias;
29
- docsBloqueantes = impacto.leituraObrigatoria
30
- .filter((doc) => doc.obrigatoriedade !== "recomendada" && doc.existe)
31
- .map((doc) => doc.relativo);
32
- docsRecomendadas = impacto.leituraRecomendada.map((doc) => doc.relativo);
33
- }
34
- const envelope = {
35
- comando: "sessao",
36
- sucesso: true,
37
- workspace: {
38
- baseProjeto: contextoProjeto.baseProjeto,
39
- nomePasta: path.basename(contextoProjeto.baseProjeto),
40
- hashContratos: frescor.hashAtual,
41
- cliVersao: pacoteCli.version,
42
- },
40
+ if (args.includes("--salvar-perfil"))
41
+ await salvarPerfilContexto(base, perfil);
42
+ const projeto = await carregarProjeto(entrada, cwd, { escopo: "projeto", adiarDescobertaCodigo: true });
43
+ const frescor = await verificarFrescorArtefatos(projeto.baseProjeto);
44
+ const pacoteCli = (await import("../package.json", { with: { type: "json" } })).default;
45
+ const arquivo = opcao(args, "--arquivo");
46
+ const contexto = frescor.fresco && intencao.trim() ? await navegarContextoSessao(projeto.baseProjeto, intencao, arquivo) : undefined;
47
+ const contrato = contexto?.candidatos.length === 1 ? contexto.candidatos[0].contrato : undefined;
48
+ const docs = contrato ? await resolverDocumentacaoObrigatoria({ baseProjeto: projeto.baseProjeto, intencao, arquivosAlvo: [contrato], criarAusentes: false }) : undefined;
49
+ const alvo = contrato ? JSON.stringify(contrato) : "<contrato>";
50
+ const payload = {
51
+ comando: "sessao", sucesso: true,
52
+ status: !frescor.fresco ? "indice_desatualizado" : contexto?.status ?? "intencao_necessaria",
53
+ execucaoAutorizada: false,
54
+ workspace: { baseProjeto: projeto.baseProjeto, nomePasta: path.basename(projeto.baseProjeto), hashContratos: frescor.hashAtual, cliVersao: pacoteCli.version },
43
55
  frescor,
44
- contratos: {
45
- total: contextoProjeto.modulosSelecionados.length,
46
- modulos: modulosNomes.slice(0, 10),
47
- },
48
- intencoes: {
49
- intencao,
50
- categorias,
51
- docsBloqueantes,
52
- docsRecomendadas,
53
- },
56
+ perfil,
57
+ contexto: contexto ? { ...contexto, candidatosOmitidos: 0 } : undefined,
58
+ intencoes: { intencao, docsBloqueantes: docs?.leituraObrigatoria.map((doc) => doc.relativo) ?? [], leituraPendente: true },
54
59
  gates: {
55
- drift: "sema drift . --escopo projeto --cache fresh --json",
56
- validar: "sema validar contratos --json",
57
- verificar: "sema verificar contratos --alvo typescript --json",
58
- finalizar: 'sema finalizar-mudanca --intencao "<intencao>" --doc-lida <doc> --json',
60
+ drift: `sema drift ${alvo} --cache fresh --json`,
61
+ validar: `sema validar ${alvo} --json`,
62
+ verificar: `sema verificar ${alvo} --alvo <alvo> --json`,
63
+ finalizar: `sema finalizar-mudanca --intencao "<intenção>" --arquivo ${alvo} --doc-lida <doc> --json`,
59
64
  },
60
- proximosPassos: [
61
- ...(!frescor.fresco ? ["Artefatos stale detectados — rode sema sync-codex --json antes de agir"] : []),
62
- ...(docsBloqueantes.length > 0 ? [`Leia antes de agir: ${docsBloqueantes.slice(0, 5).join(", ")}`] : []),
63
- ...(intencao.trim() ? ["Siga o protocolo AGENTS.md passo a passo"] : ["Declare --intencao para receber gates especificos"]),
64
- ],
65
+ proximosPassos: !frescor.fresco ? ["Rode sema sync-codex --json e repita a sessão."]
66
+ : contrato ? [`Leia ${contrato}; consulte docs-impacto com esse --arquivo para motivos e dependências. Depois inspecione apenas o código vinculado.`, "Referências do índice são navegação. Confirme regras completas e gates antes de agir."]
67
+ : ["Informe uma intenção específica ou --arquivo <contrato-ou-código> para resolver o escopo. Não edite enquanto houver ambiguidade."],
68
+ orcamento: { metodo: "chars_div_4", tokensEstimadosResposta: 0, limiteTokens: perfil.orcamentoTokens, contagemExata: false, contextoDaConversaMedido: false },
65
69
  };
66
- if (emJson) {
67
- console.log(JSON.stringify(envelope, null, 2));
68
- return 0;
69
- }
70
- console.log(`Sessao Sema — ${envelope.workspace.nomePasta}`);
71
- console.log(` Hash contratos: ${envelope.workspace.hashContratos}`);
72
- console.log(` CLI: ${envelope.workspace.cliVersao}`);
73
- console.log(` Frescor: ${frescor.fresco ? "fresco" : "STALE"} ${!frescor.fresco ? `(${frescor.sugestao})` : ""}`);
74
- console.log(` Contratos: ${envelope.contratos.total}`);
75
- if (intencao) {
76
- console.log(` Intencao: ${intencao}`);
77
- console.log(` Categorias: ${categorias.join(", ")}`);
78
- console.log(` Docs bloqueantes: ${docsBloqueantes.length}`);
79
- console.log(` Docs recomendadas: ${docsRecomendadas.length}`);
70
+ while (estimarRespostaTokens(payload) > perfil.orcamentoTokens && payload.contexto?.candidatos.length) {
71
+ payload.contexto.candidatos.pop();
72
+ payload.contexto.candidatosOmitidos++;
80
73
  }
81
- for (const passo of envelope.proximosPassos) {
82
- console.log(` → ${passo}`);
74
+ if (payload.contexto?.candidatosOmitidos) {
75
+ payload.status = "refinar_escopo";
76
+ payload.proximosPassos = ["Há candidatos omitidos pelo orçamento. Refine a intenção ou informe --arquivo; não interprete omissão como ausência de dependência."];
83
77
  }
84
- return 0;
78
+ for (let i = 0; i < 3; i++)
79
+ payload.orcamento.tokensEstimadosResposta = estimarRespostaTokens(payload);
80
+ if (payload.orcamento.tokensEstimadosResposta > perfil.orcamentoTokens)
81
+ return emitir({
82
+ comando: "sessao", sucesso: false, status: "orcamento_insuficiente", execucaoAutorizada: false,
83
+ proximosPassos: ["Metadados e leituras bloqueantes excedem o orçamento. Refine --arquivo ou ajuste --orcamento-contexto; nenhuma leitura foi dispensada."],
84
+ }, 1);
85
+ return emitir(payload);
85
86
  }
86
87
  //# sourceMappingURL=comandoSessao.js.map
@@ -0,0 +1,15 @@
1
+ export declare const ARQUIVO_PERFIL_CONTEXTO = ".sema/contexto-ia.json";
2
+ export declare const OPCOES_CONTEXTO: readonly ["--janela-contexto", "--orcamento-contexto", "--reserva-saida"];
3
+ export declare const PERGUNTA_CONTEXTO = "Qual \u00E9 a janela de contexto efetivamente configurada para esta IA, em tokens?";
4
+ export interface PerfilContexto {
5
+ schema: "sema.agent-context-profile/v1";
6
+ janelaTokens: number;
7
+ orcamentoTokens: number;
8
+ reservaSaidaTokens: number;
9
+ }
10
+ export declare function criarPerfilContexto(janela: number, orcamento?: number, reserva?: number): PerfilContexto;
11
+ export declare function numeroOpcaoContexto(args: readonly string[], nome: string): number | undefined;
12
+ export declare function lerPerfilContexto(base: string): Promise<PerfilContexto | undefined>;
13
+ export declare function resolverPerfilContexto(base: string, args: readonly string[]): Promise<PerfilContexto | undefined>;
14
+ export declare function salvarPerfilContexto(base: string, perfil: PerfilContexto): Promise<void>;
15
+ export declare function estimarRespostaTokens(payload: unknown): number;
@@ -0,0 +1,64 @@
1
+ // SEMA-GOVERNED: sema.produto.cli_sessao
2
+ // Descrição: perfil local explícito e orçamento estimado de contexto, sem inferir capacidade pelo modelo.
3
+ import { readFile } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { escreverArquivoWorkspaceSeguro, validarDestinosEscritaWorkspace } from "./workspaceWrite.js";
6
+ export const ARQUIVO_PERFIL_CONTEXTO = ".sema/contexto-ia.json";
7
+ export const OPCOES_CONTEXTO = ["--janela-contexto", "--orcamento-contexto", "--reserva-saida"];
8
+ export const PERGUNTA_CONTEXTO = "Qual é a janela de contexto efetivamente configurada para esta IA, em tokens?";
9
+ export function criarPerfilContexto(janela, orcamento, reserva) {
10
+ const perfil = {
11
+ schema: "sema.agent-context-profile/v1",
12
+ janelaTokens: janela,
13
+ orcamentoTokens: orcamento ?? Math.min(4096, Math.floor(janela / 8)),
14
+ reservaSaidaTokens: reserva ?? Math.min(4096, Math.floor(janela / 4)),
15
+ };
16
+ if (![perfil.janelaTokens, perfil.orcamentoTokens, perfil.reservaSaidaTokens].every(Number.isSafeInteger)
17
+ || janela < 4096 || perfil.orcamentoTokens < 512 || perfil.reservaSaidaTokens < 256
18
+ || perfil.orcamentoTokens + perfil.reservaSaidaTokens > janela - 1024) {
19
+ throw new Error("Perfil inválido: janela >= 4096, orçamento >= 512, reserva >= 256 e pelo menos 1024 tokens livres para instruções e conversa.");
20
+ }
21
+ return perfil;
22
+ }
23
+ export function numeroOpcaoContexto(args, nome) {
24
+ const indice = args.findIndex((arg) => arg === nome || arg.startsWith(`${nome}=`));
25
+ if (indice < 0)
26
+ return undefined;
27
+ const raw = args[indice].startsWith(`${nome}=`) ? args[indice].slice(nome.length + 1) : args[indice + 1];
28
+ if (!raw || !/^[1-9]\d*$/u.test(raw) || !Number.isSafeInteger(Number(raw))) {
29
+ throw new Error(`${nome} exige um inteiro positivo em tokens.`);
30
+ }
31
+ return Number(raw);
32
+ }
33
+ export async function lerPerfilContexto(base) {
34
+ const [destino] = await validarDestinosEscritaWorkspace(base, [ARQUIVO_PERFIL_CONTEXTO]);
35
+ if (!destino?.existe)
36
+ return undefined;
37
+ const perfil = JSON.parse(await readFile(path.join(base, ARQUIVO_PERFIL_CONTEXTO), "utf8"));
38
+ if (perfil.schema !== "sema.agent-context-profile/v1")
39
+ throw new Error("Schema do perfil de contexto inválido.");
40
+ return criarPerfilContexto(perfil.janelaTokens, perfil.orcamentoTokens, perfil.reservaSaidaTokens);
41
+ }
42
+ export async function resolverPerfilContexto(base, args) {
43
+ const janela = numeroOpcaoContexto(args, "--janela-contexto");
44
+ const orcamento = numeroOpcaoContexto(args, "--orcamento-contexto");
45
+ const reserva = numeroOpcaoContexto(args, "--reserva-saida");
46
+ const salvo = await lerPerfilContexto(base);
47
+ if (janela !== undefined)
48
+ return criarPerfilContexto(janela, orcamento, reserva);
49
+ if (!salvo) {
50
+ if (orcamento !== undefined || reserva !== undefined)
51
+ throw new Error("Informe --janela-contexto antes de ajustar o orçamento.");
52
+ return undefined;
53
+ }
54
+ return criarPerfilContexto(salvo.janelaTokens, orcamento ?? salvo.orcamentoTokens, reserva ?? salvo.reservaSaidaTokens);
55
+ }
56
+ export async function salvarPerfilContexto(base, perfil) {
57
+ await escreverArquivoWorkspaceSeguro(base, ARQUIVO_PERFIL_CONTEXTO, `${JSON.stringify(perfil, null, 2)}\n`, { sobrescrever: true });
58
+ }
59
+ // Reserva conservadora para o envelope público e a indentação que o adaptador acrescenta.
60
+ export function estimarRespostaTokens(payload) {
61
+ const envelope = { schemaVersion: "sema.cli.result/v1", ok: true, kind: "SUCCESS", command: "sessao", code: "CLI_SUCCESS", message: null, exitCode: 0, payload };
62
+ return Math.ceil((JSON.stringify(envelope, null, 2).length + 256) / 4);
63
+ }
64
+ //# sourceMappingURL=contextBudget.js.map
@@ -309,7 +309,6 @@ export function resumoConteudo(conteudo) {
309
309
  export async function adicionarDocsRaizExistentes(baseProjeto, registrar) {
310
310
  const docsRaiz = [
311
311
  { relativo: "AGENTS.md", tipo: "raiz", motivo: "regras obrigatorias do agente no projeto" },
312
- { relativo: "SEMA_INDEX.json", tipo: "raiz", motivo: "indice IA-first do projeto" },
313
312
  { relativo: "README.md", tipo: "raiz", motivo: "visao geral publica do projeto" },
314
313
  { relativo: "docs/README.md", tipo: "documentacao", motivo: "indice de documentacao" },
315
314
  ];
@@ -2,29 +2,12 @@
2
2
  // Descricao: resolve e cria documentacao obrigatoria sem escapar do workspace nem gravar lotes parcialmente.
3
3
  import { readFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
+ import { resolverRelacoesDocumentais } from "./docsRelations.js";
5
6
  import { emitirDiagnosticosArquivosOrcamento } from "./driftOrcamento.js";
6
7
  import { escreverArquivoWorkspaceSeguro, validarDestinosEscritaWorkspace, } from "./workspaceWrite.js";
7
- import { REGRAS_DOCUMENTACAO, adicionarDocsRaizExistentes, adicionarReadmesDeArquivos, caminhoEstaDentro, caminhoExiste, criarTemplateDoc, documentoPareceTemplateCriadoPelaSema, documentoTemSubstancia, inferirCategorias, listarArquivosRecursivo, normalizarRelativo, normalizarTexto, resumoConteudo, termosRelacionamentoContratos } from "./docs.part01.js";
8
+ import { REGRAS_DOCUMENTACAO, adicionarDocsRaizExistentes, adicionarReadmesDeArquivos, caminhoEstaDentro, caminhoExiste, criarTemplateDoc, documentoPareceTemplateCriadoPelaSema, documentoTemSubstancia, inferirCategorias, normalizarRelativo, resumoConteudo } from "./docs.part01.js";
8
9
  export async function adicionarContratosRelacionados(baseProjeto, intencao, arquivosAlvo, registrar) {
9
- const pastaContratos = path.join(baseProjeto, "contratos");
10
- if (!(await caminhoExiste(pastaContratos))) {
11
- return;
12
- }
13
- const termos = termosRelacionamentoContratos(intencao, arquivosAlvo);
14
- const arquivos = (await listarArquivosRecursivo(pastaContratos)).filter((arquivo) => arquivo.endsWith(".sema"));
15
- for (const arquivo of arquivos) {
16
- const relativo = normalizarRelativo(path.relative(baseProjeto, arquivo));
17
- const nome = normalizarTexto(relativo);
18
- const relacionado = termos.some((parte) => nome.includes(parte));
19
- if (relacionado) {
20
- registrar({
21
- relativo,
22
- tipo: "contrato",
23
- motivo: "contrato Sema relacionado a intencao declarada",
24
- permitirCriacao: false,
25
- });
26
- }
27
- }
10
+ await resolverRelacoesDocumentais(baseProjeto, intencao, arquivosAlvo, registrar);
28
11
  }
29
12
  export function criarRegistrador(baseProjeto) {
30
13
  const docs = [];
@@ -34,6 +17,11 @@ export function criarRegistrador(baseProjeto) {
34
17
  registrar(doc) {
35
18
  const relativo = normalizarRelativo(doc.relativo);
36
19
  const absoluto = path.resolve(baseProjeto, relativo);
20
+ if (vistos.has(relativo) && doc.obrigatoriedade !== "recomendada") {
21
+ const anterior = docs.find((item) => item.relativo === relativo);
22
+ if (anterior)
23
+ Object.assign(anterior, doc, { relativo, obrigatoriedade: "bloqueante" });
24
+ }
37
25
  if (!caminhoEstaDentro(baseProjeto, absoluto) || vistos.has(relativo)) {
38
26
  return;
39
27
  }
@@ -86,7 +74,7 @@ export async function resolverDocumentacaoObrigatoria(opcoes) {
86
74
  existe,
87
75
  criado,
88
76
  criacaoAutomatica: doc.permitirCriacao,
89
- obrigatorio: true,
77
+ obrigatorio: doc.obrigatoriedade !== "recomendada",
90
78
  obrigatoriedade: doc.obrigatoriedade ?? "bloqueante",
91
79
  template: existe ? undefined : template,
92
80
  };
@@ -100,7 +88,7 @@ export async function resolverDocumentacaoObrigatoria(opcoes) {
100
88
  }
101
89
  leituraObrigatoria.push(item);
102
90
  }
103
- const docsAusentes = leituraObrigatoria.filter((doc) => !doc.existe);
91
+ const docsAusentes = leituraObrigatoria.filter((doc) => !doc.existe && doc.obrigatoriedade !== "recomendada");
104
92
  const docsCriadas = leituraObrigatoria.filter((doc) => doc.criado);
105
93
  const bloqueios = docsAusentes.filter((doc) => doc.obrigatoriedade !== "recomendada").map((doc) => ({
106
94
  tipo: "documentacao_ausente",
@@ -116,7 +104,7 @@ export async function resolverDocumentacaoObrigatoria(opcoes) {
116
104
  intencao,
117
105
  categorias,
118
106
  arquivosAlvo,
119
- leituraObrigatoria,
107
+ leituraObrigatoria: leituraObrigatoria.filter((doc) => doc.obrigatoriedade !== "recomendada"),
120
108
  docsAusentes,
121
109
  docsCriadas,
122
110
  leituraRecomendada: leituraObrigatoria
@@ -0,0 +1,2 @@
1
+ import { type DocumentoPlanejado } from "./docs.part01.js";
2
+ export declare function resolverRelacoesDocumentais(base: string, intencao: string, alvos: string[], registrar: (doc: DocumentoPlanejado) => void): Promise<void>;
@@ -0,0 +1,83 @@
1
+ // SEMA-GOVERNED: sema.produto.governanca_ia.documentacao
2
+ // Descrição: resolve contrato exato, vínculos e dependências declaradas; palavras são apenas recomendações.
3
+ import { readFile } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { tokenizar, parsear, listarCandidatosUseRelativo } from "@sema/nucleo";
6
+ import { validarDestinosEscritaWorkspace } from "./workspaceWrite.js";
7
+ import { caminhoExiste, caminhoEstaDentro, listarArquivosRecursivo, normalizarRelativo, normalizarTexto, termosRelacionamentoContratos } from "./docs.part01.js";
8
+ export async function resolverRelacoesDocumentais(base, intencao, alvos, registrar) {
9
+ const relativo = (p) => {
10
+ const absoluto = path.resolve(base, p);
11
+ if (!caminhoEstaDentro(base, absoluto))
12
+ throw new Error("Referência documental fora do workspace.");
13
+ return normalizarRelativo(path.relative(base, absoluto));
14
+ };
15
+ const explicitos = alvos.map(relativo);
16
+ const pasta = path.join(base, "contratos");
17
+ const caminhos = await caminhoExiste(pasta) ? (await listarArquivosRecursivo(pasta, Number.POSITIVE_INFINITY)).filter((p) => p.endsWith(".sema")).map(relativo) : [];
18
+ const todos = [...new Set([...caminhos, ...explicitos.filter((p) => p.endsWith(".sema"))])];
19
+ await validarDestinosEscritaWorkspace(base, todos);
20
+ const catalogo = new Map();
21
+ for (const p of todos) {
22
+ if (!await caminhoExiste(path.join(base, p)))
23
+ continue;
24
+ const lexer = tokenizar(await readFile(path.join(base, p), "utf8"), p);
25
+ const parser = parsear(lexer.tokens);
26
+ if (parser.modulo && lexer.diagnosticos.length === 0 && parser.diagnosticos.length === 0)
27
+ catalogo.set(p, parser.modulo);
28
+ }
29
+ const fila = explicitos.filter((p) => p.endsWith(".sema"))
30
+ .map((arquivo) => ({ arquivo, motivo: "Contrato informado explicitamente em --arquivo" }));
31
+ for (const [arquivo, modulo] of catalogo) {
32
+ const donos = [modulo, ...modulo.tasks, ...modulo.routes, ...modulo.flows];
33
+ for (const dono of donos) {
34
+ for (const campo of dono.vinculos?.campos ?? []) {
35
+ if (campo.nome !== "arquivo")
36
+ continue;
37
+ const vinculo = relativo(campo.valor.replace(/^["']|["']$/g, ""));
38
+ if (explicitos.includes(vinculo))
39
+ fila.push({ arquivo, motivo: `Vínculo declarado em ${modulo.nome}: ${vinculo}` });
40
+ }
41
+ }
42
+ }
43
+ const vistos = new Set();
44
+ for (let i = 0; i < fila.length; i++) {
45
+ const item = fila[i];
46
+ if (vistos.has(item.arquivo))
47
+ continue;
48
+ vistos.add(item.arquivo);
49
+ registrar({ relativo: item.arquivo, tipo: "contrato", motivo: item.motivo, permitirCriacao: false });
50
+ const modulo = catalogo.get(item.arquivo);
51
+ if (!modulo) {
52
+ if (await caminhoExiste(path.join(base, item.arquivo)))
53
+ throw new Error(`Contrato inválido: ${item.arquivo}. Rode sema validar.`);
54
+ continue; // O resolvedor público transforma o contrato ausente em bloqueio.
55
+ }
56
+ for (const use of modulo.uses.filter((u) => u.origem === "sema")) {
57
+ let encontrados = [];
58
+ for (const nome of [use.caminho, ...listarCandidatosUseRelativo(modulo.nome, use.caminho)]) {
59
+ encontrados = [...catalogo].filter(([, m]) => m.nome === nome);
60
+ if (encontrados.length)
61
+ break;
62
+ }
63
+ if (encontrados.length !== 1)
64
+ throw new Error(`Dependência use ${use.caminho} de ${item.arquivo} ausente ou ambígua. Rode sema validar.`);
65
+ fila.push({ arquivo: encontrados[0][0], motivo: `Dependência use ${use.caminho} declarada em ${item.arquivo}` });
66
+ }
67
+ for (const campo of modulo.docs?.campos ?? []) {
68
+ if (campo.nome !== "arquivo")
69
+ continue;
70
+ const doc = relativo(campo.valor.replace(/^["']|["']$/g, ""));
71
+ const motivo = `Referência docs.arquivo declarada em ${item.arquivo}`;
72
+ registrar({ relativo: doc, tipo: doc.endsWith(".sema") ? "contrato" : "documentacao", motivo, permitirCriacao: false, obrigatoriedade: "recomendada" });
73
+ }
74
+ }
75
+ // Arquivo explícito fixa o escopo; não acrescentar contratos por palavras genéricas.
76
+ if (explicitos.length > 0)
77
+ return;
78
+ const termos = termosRelacionamentoContratos(intencao, []);
79
+ const sugestoes = [...catalogo].filter(([arquivo]) => termos.some((t) => normalizarTexto(path.basename(arquivo, ".sema")).includes(t))).slice(0, 8);
80
+ for (const [arquivo] of sugestoes)
81
+ registrar({ relativo: arquivo, tipo: "contrato", motivo: "Sugestão por intenção; confirme o contrato usando --arquivo", permitirCriacao: false, obrigatoriedade: "recomendada" });
82
+ }
83
+ //# sourceMappingURL=docsRelations.js.map
@@ -34,7 +34,7 @@ export async function principal() {
34
34
  let codigoSaida = 0;
35
35
  switch (comando) {
36
36
  case "iniciar":
37
- codigoSaida = await comandoIniciar(cwd, normalizarTemplateIniciar(obterOpcao(resto, "--template")), { force: possuiFlag(resto, "--force"), exemplosCompletos: possuiFlag(resto, "--com-exemplos") });
37
+ codigoSaida = await comandoIniciar(cwd, normalizarTemplateIniciar(obterOpcao(resto, "--template")), { force: possuiFlag(resto, "--force"), exemplosCompletos: possuiFlag(resto, "--com-exemplos"), contextoArgs: resto });
38
38
  break;
39
39
  case "sessao": {
40
40
  const { comandoSessao } = await import("./comandoSessao.js");
@@ -50,5 +50,6 @@ export declare function selecionarIndiceContexto(input: {
50
50
  mode?: string;
51
51
  drift?: string;
52
52
  sourceIndexChars?: number;
53
+ maximoModulos?: number;
53
54
  }): IndexPack;
54
55
  export {};
package/dist/indexPack.js CHANGED
@@ -84,7 +84,7 @@ export function selecionarIndiceContexto(input) {
84
84
  .map((modulo) => compactarModulo(modulo, requestTokens, projetarResumoInteiro))
85
85
  .filter((item) => item !== null)
86
86
  .sort((a, b) => b.relevanceScore - a.relevanceScore || a.module.localeCompare(b.module, "pt-BR"))
87
- .slice(0, projetarResumoInteiro ? input.modulos.length : 8);
87
+ .slice(0, input.maximoModulos ?? (projetarResumoInteiro ? input.modulos.length : 8));
88
88
  const base = {
89
89
  schema: "sema.ai.index-pack/v1",
90
90
  selection,
@@ -2,5 +2,6 @@ import { type TemplateIniciar } from "./initTemplatesBase.js";
2
2
  export declare function comandoIniciar(cwd: string, template: TemplateIniciar, opcoes?: {
3
3
  force?: boolean;
4
4
  exemplosCompletos?: boolean;
5
+ contextoArgs?: string[];
5
6
  }): Promise<number>;
6
7
  export declare function comandoInstalarExemplos(emJson: boolean): Promise<number>;
@@ -1,5 +1,6 @@
1
1
  // SEMA-GOVERNED: sema.produto.cli_init_templates, sema.produto.escrita_segura_workspace
2
2
  // Descrição: inicializa projetos preservando arquivos existentes e bloqueando escapes do workspace.
3
+ import { resolverPerfilContexto, salvarPerfilContexto, ARQUIVO_PERFIL_CONTEXTO, PERGUNTA_CONTEXTO } from "./contextBudget.js";
3
4
  import * as fs from "node:fs";
4
5
  import { readFile } from "node:fs/promises";
5
6
  import path from "node:path";
@@ -57,8 +58,14 @@ const EXEMPLOS_STARTER_INICIAR = new Set([
57
58
  "exemplos/autenticacao.sema",
58
59
  ]);
59
60
  export async function comandoIniciar(cwd, template, opcoes = {}) {
60
- const arquivosCompletos = arquivosTemplateIniciar(template);
61
61
  const configAncestral = await carregarConfiguracaoProjeto(cwd);
62
+ const basePerfil = configAncestral?.baseDiretorio ?? cwd;
63
+ const perfilContexto = await resolverPerfilContexto(basePerfil, opcoes.contextoArgs ?? []);
64
+ if (!perfilContexto) {
65
+ console.log(JSON.stringify({ comando: "iniciar", sucesso: false, status: "configuracao_necessaria", pergunta: PERGUNTA_CONTEXTO, proximoPasso: "Repita iniciar com --janela-contexto <tokens>; nenhum arquivo foi criado." }));
66
+ return 1;
67
+ }
68
+ const arquivosCompletos = arquivosTemplateIniciar(template);
62
69
  const subpastaGovernada = Boolean(configAncestral
63
70
  && path.resolve(configAncestral.baseDiretorio) !== path.resolve(cwd));
64
71
  // Em subpasta de workspace ja governado o config do template e omitido: a
@@ -84,6 +91,7 @@ export async function comandoIniciar(cwd, template, opcoes = {}) {
84
91
  ]);
85
92
  const caminhosTemplate = new Set(arquivos.map((arquivo) => arquivo.caminhoRelativo));
86
93
  const destinos = await validarDestinosEscritaWorkspace(cwd, [
94
+ ...(basePerfil === cwd ? [ARQUIVO_PERFIL_CONTEXTO] : []),
87
95
  ...caminhosTemplate,
88
96
  ...exemplosIniciar.map((arquivo) => arquivo.caminhoRelativo),
89
97
  ...ARQUIVOS_RESUMO_PROJETO_IA,
@@ -95,6 +103,7 @@ export async function comandoIniciar(cwd, template, opcoes = {}) {
95
103
  const arquivosParaEscrever = opcoes.force
96
104
  ? arquivos
97
105
  : arquivos.filter((arquivo) => !existentes.has(arquivo.caminhoRelativo));
106
+ await salvarPerfilContexto(basePerfil, perfilContexto);
98
107
  await escreverArquivos(cwd, arquivosParaEscrever, { inserirCabecalhoGovernado: true });
99
108
  const exemplos = await materializarExemplosOficiais(cwd, true, opcoes.exemplosCompletos ? undefined : EXEMPLOS_STARTER_INICIAR);
100
109
  if (!exemplos.sucesso) {
@@ -0,0 +1,13 @@
1
+ export declare function navegarContextoSessao(base: string, pedido: string, arquivo?: string): Promise<{
2
+ totalModulos: number;
3
+ indiceCaracteres: number;
4
+ candidatos: {
5
+ modulo: any;
6
+ contrato: string;
7
+ motivo: string;
8
+ resumo: any;
9
+ arquivos: any;
10
+ }[];
11
+ status: string;
12
+ execucaoAutorizada: boolean;
13
+ }>;
@@ -0,0 +1,41 @@
1
+ // SEMA-GOVERNED: sema.produto.cli_sessao
2
+ // Descrição: consulta o índice local internamente e retorna candidatos rastreáveis, sem autorização de edição.
3
+ import { readFile } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { selecionarIndiceContexto } from "./indexPack.js";
6
+ import { validarDestinosEscritaWorkspace } from "./workspaceWrite.js";
7
+ export async function navegarContextoSessao(base, pedido, arquivo) {
8
+ await validarDestinosEscritaWorkspace(base, ["SEMA_INDEX.json"]);
9
+ const texto = await readFile(path.join(base, "SEMA_INDEX.json"), "utf8");
10
+ const indice = JSON.parse(texto);
11
+ if (!Array.isArray(indice.modulos))
12
+ throw new Error("Índice sem módulos; rode sema sync-codex --json.");
13
+ const relativo = (valor) => {
14
+ const absoluto = path.resolve(base, valor);
15
+ const rel = path.relative(base, absoluto);
16
+ if (rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel))
17
+ throw new Error("Referência de contexto fora do workspace.");
18
+ return rel.replace(/\\/g, "/");
19
+ };
20
+ const alvo = arquivo ? relativo(arquivo) : undefined;
21
+ const exatos = alvo ? indice.modulos.filter((m) => relativo(m.arquivo) === alvo
22
+ || m.arquivosProvaveis?.some((p) => relativo(p) === alvo)) : [];
23
+ const pack = selecionarIndiceContexto({ modulos: indice.modulos, request: pedido, baseProject: base, sourceIndexChars: texto.length, maximoModulos: indice.modulos.length });
24
+ const encontrados = alvo ? exatos.map((m) => ({
25
+ module: m.modulo, contract: m.arquivo, purpose: m.faz, likelyFiles: m.arquivosProvaveis,
26
+ })) : pack.matches;
27
+ return {
28
+ totalModulos: indice.modulos.length,
29
+ indiceCaracteres: texto.length,
30
+ candidatos: encontrados.map((m) => ({
31
+ modulo: m.module,
32
+ contrato: relativo(m.contract),
33
+ motivo: alvo ? `Referência exata: ${alvo}` : "Correspondência de intenção; confirme no contrato antes de editar.",
34
+ resumo: m.purpose,
35
+ arquivos: (m.likelyFiles ?? []).map(relativo),
36
+ })),
37
+ status: encontrados.length === 0 ? "sem_correspondencia" : encontrados.length === 1 ? "contrato_localizado" : "selecionar_contrato",
38
+ execucaoAutorizada: false,
39
+ };
40
+ }
41
+ //# sourceMappingURL=sessionContext.js.map
@@ -60,19 +60,11 @@ while contract-only queries keep unobserved implementation fields null inside th
60
60
 
61
61
  ## Codex Context Tiers
62
62
 
63
- Codex with a small context budget should start with `SEMA_BOOT.md`,
64
- `SEMA_SMALL_MODEL.md`, `SEMA_BRIEF.micro.txt`,
65
- `AGENT_CONTEXT_PACK.json`, and `SEMA_INDEX.json`. They should stop early
66
- when a gate is unclear.
67
-
68
- Codex with a medium context budget should start with `SEMA_BOOT.md`,
69
- `AGENT_CONTEXT_PACK.json`, `SEMA_BRIEF.curto.txt`,
70
- `SEMA_INDEX.json`, and `AGENTS.md`. They must run docs-impact, drift, and
71
- impact before edits.
72
-
73
- Codex with a large context budget may consume `AGENT_CONTEXT_PACK.json`, `SEMA_BRIEF.md`,
74
- `SEMA_INDEX.json`, AST, IR, drift, and impact outputs, but larger context does
75
- not remove the contract-first rule.
63
+ All context capacities start with AGENTS.md, SEMA_BOOT.md and sema sessao.
64
+ The first session asks for the effective window in tokens. Declare --janela-contexto
65
+ and use --salvar-perfil to remember it; --orcamento-contexto and --reserva-saida are adjustable.
66
+ SEMA_INDEX.json and AGENT_CONTEXT_PACK.json are internal CLI data, never mandatory full reads.
67
+ Read the selected contract and its declared dependencies, then the linked code. Ambiguity requires refinement.
76
68
 
77
69
  ## Governed Code
78
70