@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.
- package/dist/agentBootstrap.d.ts +4 -0
- package/dist/agentBootstrap.js +44 -0
- package/dist/agentContext.d.ts +1 -1
- package/dist/agentContext.js +5 -5
- package/dist/agentContextPack.js +22 -31
- package/dist/agentEntryPoints.d.ts +1 -3
- package/dist/agentEntryPoints.js +16 -262
- package/dist/cliArgs.js +1 -0
- package/dist/cliGrammar.js +7 -3
- package/dist/cliHelpTexto.js +2 -2
- package/dist/comandoSessao.d.ts +0 -33
- package/dist/comandoSessao.js +71 -70
- package/dist/contextBudget.d.ts +15 -0
- package/dist/contextBudget.js +64 -0
- package/dist/docs.part01.js +0 -1
- package/dist/docs.part02.js +11 -23
- package/dist/docsRelations.d.ts +2 -0
- package/dist/docsRelations.js +83 -0
- package/dist/index.part08.js +1 -1
- package/dist/indexPack.d.ts +1 -0
- package/dist/indexPack.js +1 -1
- package/dist/initCommand.d.ts +1 -0
- package/dist/initCommand.js +10 -1
- package/dist/sessionContext.d.ts +13 -0
- package/dist/sessionContext.js +41 -0
- package/docs/ai-integration.md +5 -13
- package/docs/ai-workflow.md +8 -6
- package/docs/commands.md +1 -1
- package/docs/documentation.md +33 -0
- package/exemplos/python_verificacao.sema +68 -68
- package/node_modules/@sema/gerador-python/dist/runtime.js +65 -65
- package/package.json +1 -1
- package/skills/sema/SKILL.md +82 -169
package/dist/cliHelpTexto.js
CHANGED
|
@@ -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]",
|
package/dist/comandoSessao.d.ts
CHANGED
|
@@ -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>;
|
package/dist/comandoSessao.js
CHANGED
|
@@ -1,86 +1,87 @@
|
|
|
1
1
|
// SEMA-GOVERNED: sema.produto.cli_sessao
|
|
2
|
-
//
|
|
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
|
|
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
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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:
|
|
56
|
-
validar:
|
|
57
|
-
verificar:
|
|
58
|
-
finalizar:
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
package/dist/docs.part01.js
CHANGED
|
@@ -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
|
];
|
package/dist/docs.part02.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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:
|
|
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,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
|
package/dist/index.part08.js
CHANGED
|
@@ -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");
|
package/dist/indexPack.d.ts
CHANGED
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,
|
package/dist/initCommand.d.ts
CHANGED
|
@@ -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>;
|
package/dist/initCommand.js
CHANGED
|
@@ -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
|
package/docs/ai-integration.md
CHANGED
|
@@ -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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|