@semacode/cli 3.12.0 → 3.12.2

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.
@@ -687,7 +687,7 @@ Contract edit rule: \`.sema\` has its own size budget. Above ${LIMITE_AVISO_LINH
687
687
 
688
688
  Closing rule: the \`payload\` of \`sema drift --cache fresh --json\` must return \`sucesso:true\`. If it reports \`sucesso:false\`, \`vinculos_quebrados\`, \`rotas_divergentes\`, or \`impls_quebrados\`, the task is still blocked. Passing unit tests or a cache hit do not replace fresh green drift. In 3.0.0, \`ok\` describes the CLI result path and never replaces a command-specific domain verdict.
689
689
 
690
- Focused drift rule: file and module scopes plan their physical file set before cataloging. They never fall back to a whole-project walk when no safe anchor exists. All indexers and semantic-budget checks reuse the same in-memory source read; a global walk is reserved for explicit \`--escopo projeto\`. Code discovery is deferred until that plan exists, homonymous candidates fail as ambiguous, and configured contract origins or code roots resolving outside the workspace are rejected before enumeration. \`inspecionar\`, \`impacto\`, and \`renomear-semantico\` preserve the same directed boundary. Missing local dependencies make coverage partial and block success.
690
+ Focused drift rule: file and module scopes plan their physical file set before cataloging. They never fall back to a whole-project walk when no safe anchor exists. All indexers and semantic-budget checks reuse the same in-memory source read; a global walk is reserved for explicit \`--escopo projeto\`. Code discovery is deferred until that plan exists, homonymous candidates fail as ambiguous, and configured contract origins or code roots resolving outside the workspace are rejected before enumeration. \`inspecionar\`, \`impacto\`, and \`renomear-semantico\` preserve the same directed boundary. Missing unambiguous local dependencies make coverage partial and block success. In Python, an unresolved \`name\` from \`from module import name\` is not itself a missing file after \`module\` resolves: it may be an exported symbol, while a real child module is still included whenever its file exists.
691
691
 
692
692
  Cache rule: a closure gate should use \`sema drift <contrato> --escopo modulo --cache fresh --json\`. \`--cache none\` executes the same analysis without persistent-cache I/O, while \`--cache cache\` may reuse only extraction data whose schema, workspace identity, Git HEAD, plan, configuration, contracts, member paths, and strong content digests all match. Cache objects are stored in the operating system's user-cache directory, never below the workspace. A hit is acceleration, not evidence: final links, diagnostics, score, and success are always recalculated. Corruption or an unavailable cache degrades to an in-memory calculation.
693
693
 
@@ -774,7 +774,7 @@ Then read every required doc returned by \`docs-impacto\`.
774
774
 
775
775
  Honest closure: unwrap drift JSON and treat its command payload as the source of truth. \`payload.sucesso:false\`, non-empty \`payload.vinculos_quebrados\`, non-empty \`payload.rotas_divergentes\`, or non-empty \`payload.impls_quebrados\` mean the change is not complete yet. Do not report "clean drift" without a green payload.
776
776
 
777
- Focused drift exposes its planned, declared, inferred, and missing files plus catalog visit/read metrics in \`escopo_aplicado\`. File and module scopes fail closed without a safe anchor, with homonymous implementation candidates, or with missing local dependencies; only \`--escopo projeto\` may walk every configured code root. Logical roots such as \`src\` are probed deterministically without a discovery walk. Configured contract origins and code roots are confined before enumeration, and \`inspecionar\`, \`impacto\`, and \`renomear-semantico\` reuse the same directed boundary without reopening arbitrary external paths.
777
+ Focused drift exposes its planned, declared, inferred, and missing files plus catalog visit/read metrics in \`escopo_aplicado\`. A missing declared file or a missing unambiguous code-referenced dependency is a blocker reported as \`vinculo_quebrado\`; a missing inferred candidate derived from an \`impl\` symbol is informational under \`arquivosAusentesInferidos\` and never blocks an implementation that resolved. For Python \`from module import name\`, Sema still includes \`name\` when it resolves as a real submodule, but never reports the absent submodule candidate as a broken link after the parent module resolved because \`name\` may be a function, class, or variable. File and module scopes fail closed without a safe anchor, with homonymous implementation candidates, or with missing local dependencies; only \`--escopo projeto\` may walk every configured code root. Logical roots such as \`src\` are probed deterministically without a discovery walk. Configured contract origins and code roots are confined before enumeration, and \`inspecionar\`, \`impacto\`, and \`renomear-semantico\` reuse the same directed boundary without reopening arbitrary external paths.
778
778
 
779
779
  Drift analysis and cache modes are explicit:
780
780
 
@@ -177,6 +177,7 @@ export interface ConfiguracaoEscopoDriftAplicada {
177
177
  arquivosDeclarados?: string[];
178
178
  arquivosInferidos?: string[];
179
179
  arquivosAusentes?: string[];
180
+ arquivosAusentesInferidos?: string[];
180
181
  bloqueios?: string[];
181
182
  catalogo?: MetricasCatalogoDrift;
182
183
  cache?: EstadoCacheDriftAplicado;
@@ -148,6 +148,7 @@ export async function analisarDriftLegado(contexto, opcoes) {
148
148
  configuracaoEscopo.arquivosDeclarados = planoEscopo.arquivosDeclarados.map(relativoEscopo);
149
149
  configuracaoEscopo.arquivosInferidos = planoEscopo.arquivosInferidos.map(relativoEscopo);
150
150
  configuracaoEscopo.arquivosAusentes = planoEscopo.arquivosAusentes.map(relativoEscopo);
151
+ configuracaoEscopo.arquivosAusentesInferidos = planoEscopo.arquivosAusentesInferidos.map(relativoEscopo);
151
152
  const bloqueiosAnalise = new Set(planoEscopo.bloqueios);
152
153
  configuracaoEscopo.bloqueios = [...bloqueiosAnalise];
153
154
  configuracaoEscopo.catalogo = metricasCatalogo;
@@ -495,6 +495,7 @@ function representarPlanoCache(contexto, configuracao, plano) {
495
495
  arquivosDeclarados: ordenar(plano.arquivosDeclarados),
496
496
  arquivosInferidos: ordenar(plano.arquivosInferidos),
497
497
  arquivosAusentes: ordenar(plano.arquivosAusentes),
498
+ arquivosAusentesInferidos: ordenar(plano.arquivosAusentesInferidos),
498
499
  diretorios: ordenar(plano.diretorios),
499
500
  diretoriosCodigo: ordenar(contexto.diretoriosCodigo),
500
501
  ignorarWorktrees: configuracao.ignorarWorktrees,
@@ -11,6 +11,7 @@ export interface PlanoEscopoDrift {
11
11
  arquivosDeclarados: string[];
12
12
  arquivosInferidos: string[];
13
13
  arquivosAusentes: string[];
14
+ arquivosAusentesInferidos: string[];
14
15
  diretorios: string[];
15
16
  modulos: string[];
16
17
  cobertura: CoberturaEscopoDrift;
@@ -365,6 +365,7 @@ export async function planejarEscopoDrift(contexto, configuracao) {
365
365
  arquivosDeclarados: [],
366
366
  arquivosInferidos: [],
367
367
  arquivosAusentes: [],
368
+ arquivosAusentesInferidos: [],
368
369
  diretorios,
369
370
  modulos: contexto.modulosSelecionados
370
371
  .map((modulo) => modulo.resultado.ir?.nome)
@@ -382,6 +383,7 @@ export async function planejarEscopoDrift(contexto, configuracao) {
382
383
  .filter((ir) => Boolean(ir));
383
384
  const declarados = new Map();
384
385
  const ausentes = new Map();
386
+ const ausentesInferidos = new Map();
385
387
  const inferidos = new Map();
386
388
  const bloqueiosPlano = new Set();
387
389
  const raizReal = await realpath(contexto.baseProjeto);
@@ -440,7 +442,9 @@ export async function planejarEscopoDrift(contexto, configuracao) {
440
442
  }
441
443
  const primeiroCandidato = gruposCandidatos[0]?.[0];
442
444
  if (encontrados.length === 0 && primeiroCandidato) {
443
- ausentes.set(chaveCaminho(primeiroCandidato), primeiroCandidato);
445
+ // Candidato derivado do simbolo do impl, nao uma promessa do contrato:
446
+ // ausencia aqui e informacao de ancoragem, nunca vinculo quebrado.
447
+ ausentesInferidos.set(chaveCaminho(primeiroCandidato), primeiroCandidato);
444
448
  }
445
449
  }
446
450
  const raizesAncoradas = escolherDiretoriosLogicos(contexto, [...declarados.values(), ...inferidos.values()]);
@@ -470,6 +474,7 @@ export async function planejarEscopoDrift(contexto, configuracao) {
470
474
  }
471
475
  const arquivos = [...arquivosMesclados.values()].sort((a, b) => a.localeCompare(b, "pt-BR"));
472
476
  const arquivosAusentes = [...ausentes.values()].sort((a, b) => a.localeCompare(b, "pt-BR"));
477
+ const arquivosAusentesInferidos = [...ausentesInferidos.values()].sort((a, b) => a.localeCompare(b, "pt-BR"));
473
478
  const exigeAncoraCodigo = irs.some(moduloExigeAncoraCodigo);
474
479
  if (arquivos.length === 0
475
480
  && exigeAncoraCodigo
@@ -484,6 +489,7 @@ export async function planejarEscopoDrift(contexto, configuracao) {
484
489
  arquivosDeclarados: [...declarados.values()].sort((a, b) => a.localeCompare(b, "pt-BR")),
485
490
  arquivosInferidos: [...inferidos.values()].sort((a, b) => a.localeCompare(b, "pt-BR")),
486
491
  arquivosAusentes,
492
+ arquivosAusentesInferidos,
487
493
  diretorios: escolherDiretoriosLogicos(contexto, arquivos),
488
494
  modulos: irs.map((ir) => ir.nome),
489
495
  cobertura: arquivosAusentes.length === 0 && bloqueios.length === 0 ? "completa" : "parcial",
@@ -564,7 +570,12 @@ async function resolverReferenciasLocais(contexto, arquivoOrigem, referencias, c
564
570
  }
565
571
  const modulo = item.referencia.slice("python:".length);
566
572
  const prefixoLocalResolvido = modulosPythonResolvidos.some((prefixo) => prefixo !== modulo && modulo.startsWith(`${prefixo}.`));
567
- if (!referenciaPythonEhLocalObrigatoria(item.referencia) && !prefixoLocalResolvido) {
573
+ if (prefixoLocalResolvido) {
574
+ // Em `from modulo import nome`, `nome` pode ser simbolo exportado ou submodulo.
575
+ // Inclua o submodulo quando existir, mas sua ausencia nao prova dependencia quebrada.
576
+ return { referencia: item.referencia };
577
+ }
578
+ if (!referenciaPythonEhLocalObrigatoria(item.referencia)) {
568
579
  return item;
569
580
  }
570
581
  const candidatos = candidatosReferenciaLocalDrift(contexto, arquivoOrigem, item.referencia);
@@ -20,7 +20,7 @@ Contract edit rule: `.sema` has its own size budget. Above 300 lines, plan a spl
20
20
 
21
21
  Closing rule: the `payload` of `sema drift --cache fresh --json` must return `sucesso:true`. If it reports `sucesso:false`, `vinculos_quebrados`, `rotas_divergentes`, or `impls_quebrados`, the task is still blocked. Passing unit tests or a cache hit do not replace fresh green drift. In 3.0.0, `ok` describes the CLI result path and never replaces a command-specific domain verdict.
22
22
 
23
- Focused drift rule: file and module scopes plan their physical file set before cataloging. They never fall back to a whole-project walk when no safe anchor exists. All indexers and semantic-budget checks reuse the same in-memory source read; a global walk is reserved for explicit `--escopo projeto`. Code discovery is deferred until that plan exists, homonymous candidates fail as ambiguous, and configured contract origins or code roots resolving outside the workspace are rejected before enumeration. `inspecionar`, `impacto`, and `renomear-semantico` preserve the same directed boundary. Missing local dependencies make coverage partial and block success.
23
+ Focused drift rule: file and module scopes plan their physical file set before cataloging. They never fall back to a whole-project walk when no safe anchor exists. All indexers and semantic-budget checks reuse the same in-memory source read; a global walk is reserved for explicit `--escopo projeto`. Code discovery is deferred until that plan exists, homonymous candidates fail as ambiguous, and configured contract origins or code roots resolving outside the workspace are rejected before enumeration. `inspecionar`, `impacto`, and `renomear-semantico` preserve the same directed boundary. Missing unambiguous local dependencies make coverage partial and block success. In Python, an unresolved `name` from `from module import name` is not itself a missing file after `module` resolves: it may be an exported symbol, while a real child module is still included whenever its file exists.
24
24
 
25
25
  Cache rule: a closure gate should use `sema drift <contrato> --escopo modulo --cache fresh --json`. `--cache none` executes the same analysis without persistent-cache I/O, while `--cache cache` may reuse only extraction data whose schema, workspace identity, Git HEAD, plan, configuration, contracts, member paths, and strong content digests all match. Cache objects are stored in the operating system's user-cache directory, never below the workspace. A hit is acceleration, not evidence: final links, diagnostics, score, and success are always recalculated. Corruption or an unavailable cache degrades to an in-memory calculation.
26
26
 
package/docs/commands.md CHANGED
@@ -53,7 +53,7 @@ Then read every required doc returned by `docs-impacto`.
53
53
 
54
54
  Honest closure: unwrap drift JSON and treat its command payload as the source of truth. `payload.sucesso:false`, non-empty `payload.vinculos_quebrados`, non-empty `payload.rotas_divergentes`, or non-empty `payload.impls_quebrados` mean the change is not complete yet. Do not report "clean drift" without a green payload.
55
55
 
56
- Focused drift exposes its planned, declared, inferred, and missing files plus catalog visit/read metrics in `escopo_aplicado`. File and module scopes fail closed without a safe anchor, with homonymous implementation candidates, or with missing local dependencies; only `--escopo projeto` may walk every configured code root. Logical roots such as `src` are probed deterministically without a discovery walk. Configured contract origins and code roots are confined before enumeration, and `inspecionar`, `impacto`, and `renomear-semantico` reuse the same directed boundary without reopening arbitrary external paths.
56
+ Focused drift exposes its planned, declared, inferred, and missing files plus catalog visit/read metrics in `escopo_aplicado`. A missing declared file or a missing unambiguous code-referenced dependency is a blocker reported as `vinculo_quebrado`; a missing inferred candidate derived from an `impl` symbol is informational under `arquivosAusentesInferidos` and never blocks an implementation that resolved. For Python `from module import name`, Sema still includes `name` when it resolves as a real submodule, but never reports the absent submodule candidate as a broken link after the parent module resolved because `name` may be a function, class, or variable. File and module scopes fail closed without a safe anchor, with homonymous implementation candidates, or with missing local dependencies; only `--escopo projeto` may walk every configured code root. Logical roots such as `src` are probed deterministically without a discovery walk. Configured contract origins and code roots are confined before enumeration, and `inspecionar`, `impacto`, and `renomear-semantico` reuse the same directed boundary without reopening arbitrary external paths.
57
57
 
58
58
  Drift analysis and cache modes are explicit:
59
59
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@semacode/cli",
3
- "version": "3.12.0",
3
+ "version": "3.12.2",
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",