@semacode/cli 1.5.0 → 1.5.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.
- package/README.md +1 -1
- package/dist/drift.d.ts +8 -4
- package/dist/drift.js +332 -38
- package/dist/drift.js.map +1 -1
- package/dist/index.js +221 -18
- package/dist/index.js.map +1 -1
- package/docs/cli.md +1 -1
- package/docs/persistencia-vendor-first.md +1 -1
- package/node_modules/@sema/gerador-css/package.json +1 -1
- package/node_modules/@sema/gerador-dart/package.json +1 -1
- package/node_modules/@sema/gerador-html/package.json +1 -1
- package/node_modules/@sema/gerador-javascript/package.json +1 -1
- package/node_modules/@sema/gerador-lua/package.json +1 -1
- package/node_modules/@sema/gerador-python/package.json +1 -1
- package/node_modules/@sema/gerador-typescript/package.json +1 -1
- package/node_modules/@sema/nucleo/package.json +1 -1
- package/node_modules/@sema/padroes/package.json +1 -1
- package/package.json +11 -10
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ sema drift ./contratos-importados --json
|
|
|
58
58
|
|
|
59
59
|
## Persistencia vendor-first
|
|
60
60
|
|
|
61
|
-
A CLI 1.5.
|
|
61
|
+
A CLI 1.5.1 entende blocos `database` e recursos de persistencia no IR, no formatador, no semantico, na importacao, no drift, no impact map e na renomeacao semantica assistida. O objetivo nao e esconder diferencas entre bancos, e sim capturar essas diferencas no contrato.
|
|
62
62
|
|
|
63
63
|
Cobertura publica:
|
|
64
64
|
|
package/dist/drift.d.ts
CHANGED
|
@@ -42,8 +42,9 @@ interface RegistroRotaDivergente {
|
|
|
42
42
|
caminho?: string;
|
|
43
43
|
motivo: string;
|
|
44
44
|
}
|
|
45
|
-
type OrigemRecursoDrift = "firebase" | EngineBanco;
|
|
46
|
-
type TipoRecursoDrift = "colecao" | TipoRecursoPersistencia;
|
|
45
|
+
type OrigemRecursoDrift = "firebase" | EngineBanco | "arquivo";
|
|
46
|
+
type TipoRecursoDrift = "colecao" | TipoRecursoPersistencia | "arquivo_local";
|
|
47
|
+
type CategoriaPersistenciaDrift = "relacional" | "documental" | "chave_valor" | "local_arquivo" | "desconhecida";
|
|
47
48
|
interface RecursoResolvido {
|
|
48
49
|
origem: OrigemRecursoDrift;
|
|
49
50
|
nome: string;
|
|
@@ -62,13 +63,15 @@ interface RegistroRecursoDrift {
|
|
|
62
63
|
status: "resolvido" | "divergente";
|
|
63
64
|
}
|
|
64
65
|
interface RegistroColunaPersistenciaDrift {
|
|
65
|
-
|
|
66
|
+
origem: OrigemRecursoDrift;
|
|
67
|
+
categoriaPersistencia: CategoriaPersistenciaDrift;
|
|
66
68
|
recurso: string;
|
|
67
69
|
coluna: string;
|
|
68
70
|
arquivo: string;
|
|
69
71
|
}
|
|
70
72
|
interface RegistroRepositorioPersistenciaDrift {
|
|
71
|
-
|
|
73
|
+
origem: OrigemRecursoDrift;
|
|
74
|
+
categoriaPersistencia: CategoriaPersistenciaDrift;
|
|
72
75
|
recurso: string;
|
|
73
76
|
arquivo: string;
|
|
74
77
|
}
|
|
@@ -113,6 +116,7 @@ export interface RegistroPersistenciaRealDrift {
|
|
|
113
116
|
task: string;
|
|
114
117
|
alvo: string;
|
|
115
118
|
engine: OrigemRecursoDrift | "desconhecido";
|
|
119
|
+
categoriaPersistencia: CategoriaPersistenciaDrift;
|
|
116
120
|
tipo: TipoRecursoDrift;
|
|
117
121
|
status: "materializado" | "parcial" | "divergente";
|
|
118
122
|
arquivos: string[];
|
package/dist/drift.js
CHANGED
|
@@ -56,6 +56,7 @@ const TERMOS_ESCopo_IGNORADOS = new Set([
|
|
|
56
56
|
"data",
|
|
57
57
|
"drift",
|
|
58
58
|
"flow",
|
|
59
|
+
"int",
|
|
59
60
|
"module",
|
|
60
61
|
"modulo",
|
|
61
62
|
"publico",
|
|
@@ -110,18 +111,29 @@ function quebrarTermosEscopo(valor) {
|
|
|
110
111
|
.map((item) => item.trim())
|
|
111
112
|
.filter((item) => item.length >= 3 && !TERMOS_ESCopo_IGNORADOS.has(item));
|
|
112
113
|
}
|
|
114
|
+
function quebrarTermosModuloEscopo(nomeModulo) {
|
|
115
|
+
const segmentos = nomeModulo
|
|
116
|
+
.split(".")
|
|
117
|
+
.map((item) => item.trim())
|
|
118
|
+
.filter(Boolean);
|
|
119
|
+
const relevantes = segmentos.length > 1 ? segmentos.slice(1) : segmentos;
|
|
120
|
+
return relevantes.flatMap((segmento) => quebrarTermosEscopo(segmento));
|
|
121
|
+
}
|
|
113
122
|
function extrairTermosEscopoDrift(contexto, escopo) {
|
|
114
123
|
if (escopo === "projeto") {
|
|
115
124
|
return [];
|
|
116
125
|
}
|
|
117
126
|
const termos = new Set();
|
|
118
|
-
|
|
127
|
+
const termosRaizProjeto = new Set(quebrarTermosEscopo(path.basename(contexto.baseProjeto)));
|
|
128
|
+
if (escopo === "arquivo" || path.extname(contexto.entradaResolvida)) {
|
|
129
|
+
termos.add(paraIdentificadorModulo(path.basename(contexto.entradaResolvida, path.extname(contexto.entradaResolvida))));
|
|
130
|
+
}
|
|
119
131
|
for (const modulo of contexto.modulosSelecionados) {
|
|
120
132
|
const ir = modulo.resultado.ir;
|
|
121
133
|
if (!ir) {
|
|
122
134
|
continue;
|
|
123
135
|
}
|
|
124
|
-
for (const termo of
|
|
136
|
+
for (const termo of quebrarTermosModuloEscopo(ir.nome)) {
|
|
125
137
|
termos.add(termo);
|
|
126
138
|
}
|
|
127
139
|
for (const task of ir.tasks) {
|
|
@@ -140,7 +152,26 @@ function extrairTermosEscopoDrift(contexto, escopo) {
|
|
|
140
152
|
}
|
|
141
153
|
}
|
|
142
154
|
}
|
|
143
|
-
return [...termos]
|
|
155
|
+
return [...termos]
|
|
156
|
+
.filter((termo) => Boolean(termo) && !termosRaizProjeto.has(termo))
|
|
157
|
+
.sort((a, b) => a.localeCompare(b, "pt-BR"));
|
|
158
|
+
}
|
|
159
|
+
function categorizarPersistenciaPorOrigem(origem) {
|
|
160
|
+
switch (origem) {
|
|
161
|
+
case "postgres":
|
|
162
|
+
case "mysql":
|
|
163
|
+
case "sqlite":
|
|
164
|
+
return "relacional";
|
|
165
|
+
case "mongodb":
|
|
166
|
+
case "firebase":
|
|
167
|
+
return "documental";
|
|
168
|
+
case "redis":
|
|
169
|
+
return "chave_valor";
|
|
170
|
+
case "arquivo":
|
|
171
|
+
return "local_arquivo";
|
|
172
|
+
default:
|
|
173
|
+
return "desconhecida";
|
|
174
|
+
}
|
|
144
175
|
}
|
|
145
176
|
function caminhoTemSegmentoIgnorado(arquivo, segmentos) {
|
|
146
177
|
const partes = normalizarFragmentoArquivo(arquivo).split("/").filter(Boolean);
|
|
@@ -210,9 +241,104 @@ function textoCombinaEscopo(texto, termos) {
|
|
|
210
241
|
const normalizado = paraIdentificadorModulo(texto);
|
|
211
242
|
return termos.some((termo) => normalizado.includes(termo));
|
|
212
243
|
}
|
|
213
|
-
function
|
|
244
|
+
function construirContextoRelevanciaConsumer(contexto, tasksResumo, vinculosValidos) {
|
|
245
|
+
const arquivosAncora = new Set();
|
|
246
|
+
const rotasAncora = new Set();
|
|
247
|
+
for (const modulo of contexto.modulosSelecionados) {
|
|
248
|
+
const ir = modulo.resultado.ir;
|
|
249
|
+
if (!ir) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
for (const route of ir.routes) {
|
|
253
|
+
if (route.caminho) {
|
|
254
|
+
rotasAncora.add(normalizarCaminhoRota(route.caminho));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
for (const task of tasksResumo) {
|
|
259
|
+
for (const arquivo of [...task.arquivosReferenciados, ...task.arquivosProvaveisEditar]) {
|
|
260
|
+
arquivosAncora.add(arquivo);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
for (const vinculo of vinculosValidos) {
|
|
264
|
+
if (vinculo.arquivo) {
|
|
265
|
+
arquivosAncora.add(vinculo.arquivo);
|
|
266
|
+
}
|
|
267
|
+
if (vinculo.tipo === "superficie" || vinculo.tipo === "rota") {
|
|
268
|
+
rotasAncora.add(normalizarCaminhoRota(vinculo.valor));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
arquivosAncora: [...arquivosAncora].sort((a, b) => a.localeCompare(b, "pt-BR")),
|
|
273
|
+
rotasAncora: [...rotasAncora].sort((a, b) => a.localeCompare(b, "pt-BR")),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function pontuarTextoEscopo(texto, termos) {
|
|
277
|
+
if (termos.length === 0) {
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
280
|
+
const normalizado = paraIdentificadorModulo(texto);
|
|
281
|
+
const segmentos = new Set(normalizado.split("_").filter(Boolean));
|
|
282
|
+
let score = 0;
|
|
283
|
+
for (const termo of termos) {
|
|
284
|
+
if (segmentos.has(termo)) {
|
|
285
|
+
score += 4;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (normalizado.includes(termo)) {
|
|
289
|
+
score += 2;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return Math.min(score, 8);
|
|
293
|
+
}
|
|
294
|
+
function pontuarProximidadeArquivoConsumer(arquivo, arquivosAncora) {
|
|
295
|
+
if (arquivosAncora.length === 0) {
|
|
296
|
+
return 0;
|
|
297
|
+
}
|
|
298
|
+
const alvo = normalizarFragmentoArquivo(arquivo);
|
|
299
|
+
const diretorioAlvo = path.posix.dirname(alvo);
|
|
300
|
+
let scoreMaximo = 0;
|
|
301
|
+
for (const ancora of arquivosAncora) {
|
|
302
|
+
const ancoraNormalizada = normalizarFragmentoArquivo(ancora);
|
|
303
|
+
const diretorioAncora = path.posix.dirname(ancoraNormalizada);
|
|
304
|
+
if (alvo === ancoraNormalizada) {
|
|
305
|
+
return 8;
|
|
306
|
+
}
|
|
307
|
+
if (diretorioAlvo === diretorioAncora) {
|
|
308
|
+
scoreMaximo = Math.max(scoreMaximo, 6);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
const ultimoDiretorioAlvo = diretorioAlvo.split("/").filter(Boolean).at(-1);
|
|
312
|
+
const ultimoDiretorioAncora = diretorioAncora.split("/").filter(Boolean).at(-1);
|
|
313
|
+
if (ultimoDiretorioAlvo && ultimoDiretorioAlvo === ultimoDiretorioAncora) {
|
|
314
|
+
scoreMaximo = Math.max(scoreMaximo, 4);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return scoreMaximo;
|
|
318
|
+
}
|
|
319
|
+
function pontuarProximidadeRotaConsumer(rota, rotasAncora) {
|
|
320
|
+
if (rotasAncora.length === 0) {
|
|
321
|
+
return 0;
|
|
322
|
+
}
|
|
323
|
+
const rotaNormalizada = normalizarCaminhoRota(rota);
|
|
324
|
+
const segmentosRota = rotaNormalizada.split("/").filter(Boolean);
|
|
325
|
+
let scoreMaximo = 0;
|
|
326
|
+
for (const ancora of rotasAncora) {
|
|
327
|
+
const rotaAncora = normalizarCaminhoRota(ancora);
|
|
328
|
+
if (rotaNormalizada === rotaAncora) {
|
|
329
|
+
return 8;
|
|
330
|
+
}
|
|
331
|
+
const segmentosAncora = rotaAncora.split("/").filter(Boolean);
|
|
332
|
+
if (segmentosRota[0] && segmentosRota[0] === segmentosAncora[0]) {
|
|
333
|
+
scoreMaximo = Math.max(scoreMaximo, 4);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return scoreMaximo;
|
|
337
|
+
}
|
|
338
|
+
function filtrarConsumerSurfacesPorEscopo(consumerSurfaces, consumerBridges, contexto, configuracao, relevancia) {
|
|
214
339
|
const raizesWorktreePermitidas = resolverRaizesIgnoradasPermitidas(contexto, DIRETORIOS_WORKTREE);
|
|
215
340
|
const raizesConsumidorPermitidas = resolverRaizesIgnoradasPermitidas(contexto, DIRETORIOS_CONSUMIDOR_LATERAL);
|
|
341
|
+
const limiar = configuracao.escopo === "arquivo" ? 5 : 4;
|
|
216
342
|
const manterSurface = (surface) => {
|
|
217
343
|
if (configuracao.ignorarWorktrees
|
|
218
344
|
&& caminhoIgnoradoForaDoEscopoReal(surface.arquivo, DIRETORIOS_WORKTREE, raizesWorktreePermitidas)) {
|
|
@@ -222,11 +348,13 @@ function filtrarConsumerSurfacesPorEscopo(consumerSurfaces, consumerBridges, con
|
|
|
222
348
|
&& caminhoIgnoradoForaDoEscopoReal(surface.arquivo, DIRETORIOS_CONSUMIDOR_LATERAL, raizesConsumidorPermitidas)) {
|
|
223
349
|
return false;
|
|
224
350
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return combinaEscopo || configuracao.escopo === "projeto";
|
|
351
|
+
if (configuracao.escopo === "projeto") {
|
|
352
|
+
return true;
|
|
228
353
|
}
|
|
229
|
-
|
|
354
|
+
const score = pontuarTextoEscopo(`${surface.rota} ${surface.arquivo} ${surface.tipoArquivo}`, configuracao.termosEscopo)
|
|
355
|
+
+ pontuarProximidadeArquivoConsumer(surface.arquivo, relevancia?.arquivosAncora ?? [])
|
|
356
|
+
+ pontuarProximidadeRotaConsumer(surface.rota, relevancia?.rotasAncora ?? []);
|
|
357
|
+
return score >= limiar;
|
|
230
358
|
};
|
|
231
359
|
const manterBridge = (bridge) => {
|
|
232
360
|
if (configuracao.ignorarWorktrees
|
|
@@ -237,11 +365,12 @@ function filtrarConsumerSurfacesPorEscopo(consumerSurfaces, consumerBridges, con
|
|
|
237
365
|
&& caminhoIgnoradoForaDoEscopoReal(bridge.arquivo, DIRETORIOS_CONSUMIDOR_LATERAL, raizesConsumidorPermitidas)) {
|
|
238
366
|
return false;
|
|
239
367
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
return combinaEscopo || configuracao.escopo === "projeto";
|
|
368
|
+
if (configuracao.escopo === "projeto") {
|
|
369
|
+
return true;
|
|
243
370
|
}
|
|
244
|
-
|
|
371
|
+
const score = pontuarTextoEscopo(`${bridge.caminho} ${bridge.arquivo} ${bridge.simbolo}`, configuracao.termosEscopo)
|
|
372
|
+
+ pontuarProximidadeArquivoConsumer(bridge.arquivo, relevancia?.arquivosAncora ?? []);
|
|
373
|
+
return score >= limiar;
|
|
245
374
|
};
|
|
246
375
|
return {
|
|
247
376
|
consumerSurfaces: consumerSurfaces.filter(manterSurface),
|
|
@@ -542,6 +671,29 @@ function extrairRecursosRedis(arquivo, codigo) {
|
|
|
542
671
|
}
|
|
543
672
|
return [...recursos.values()];
|
|
544
673
|
}
|
|
674
|
+
function extrairRecursosArquivoLocal(arquivo, codigo) {
|
|
675
|
+
const recursos = new Map();
|
|
676
|
+
const contextoArquivoLocal = /\b(?:json|jsonl|ndjson)\b/i.test(codigo)
|
|
677
|
+
|| /\b(?:read_text|write_text|readFile(?:Sync)?|writeFile(?:Sync)?|open)\b/i.test(codigo)
|
|
678
|
+
|| /\.(?:json|jsonl|ndjson|db|sqlite|sqlite3)\b/i.test(codigo)
|
|
679
|
+
|| /(?:repository|repositories|repositorio|repo|store|storage|persist|cache)/i.test(normalizarFragmentoArquivo(arquivo));
|
|
680
|
+
if (!contextoArquivoLocal) {
|
|
681
|
+
return [];
|
|
682
|
+
}
|
|
683
|
+
for (const match of codigo.matchAll(/["'`]([^"'`]+\.(?:json|jsonl|ndjson|db|sqlite|sqlite3))["'`]/gi)) {
|
|
684
|
+
const literal = match[1] ?? "";
|
|
685
|
+
const nomeBase = path.basename(literal, path.extname(literal));
|
|
686
|
+
registrarRecursoDrift(recursos, "arquivo", "arquivo_local", nomeBase, arquivo);
|
|
687
|
+
}
|
|
688
|
+
const nomeArquivo = path.basename(arquivo).replace(/\.(?:ts|tsx|js|jsx|mjs|cjs|py|dart|cs|java|go|rs|cpp|cc|cxx|hpp|h)$/i, "");
|
|
689
|
+
const nomeStore = nomeArquivo
|
|
690
|
+
.replace(/(?:[_.-]?(?:repository|repositories|repo|store|storage|persistencia|persistence))$/i, "")
|
|
691
|
+
.trim();
|
|
692
|
+
if (nomeStore && /(?:repository|repositories|repositorio|repo|store|storage|persist|cache)/i.test(nomeArquivo)) {
|
|
693
|
+
registrarRecursoDrift(recursos, "arquivo", "arquivo_local", nomeStore, arquivo);
|
|
694
|
+
}
|
|
695
|
+
return [...recursos.values()];
|
|
696
|
+
}
|
|
545
697
|
function extrairRecursosPersistenciaCodigoVivo(arquivo, codigo) {
|
|
546
698
|
const recursos = new Map();
|
|
547
699
|
for (const recurso of extrairColecoesFirebase(arquivo, codigo)) {
|
|
@@ -556,6 +708,9 @@ function extrairRecursosPersistenciaCodigoVivo(arquivo, codigo) {
|
|
|
556
708
|
for (const recurso of extrairRecursosRedis(arquivo, codigo)) {
|
|
557
709
|
registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
|
|
558
710
|
}
|
|
711
|
+
for (const recurso of extrairRecursosArquivoLocal(arquivo, codigo)) {
|
|
712
|
+
registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
|
|
713
|
+
}
|
|
559
714
|
return [...recursos.values()];
|
|
560
715
|
}
|
|
561
716
|
function extrairRecursosPrisma(arquivo, codigo) {
|
|
@@ -1757,7 +1912,11 @@ async function indexarCpp(diretorios) {
|
|
|
1757
1912
|
async function indexarPersistenciaDeclarativa(diretorios) {
|
|
1758
1913
|
const recursos = new Map();
|
|
1759
1914
|
for (const diretorio of diretorios) {
|
|
1760
|
-
const arquivos = await listarArquivosRecursivos(diretorio, [
|
|
1915
|
+
const arquivos = await listarArquivosRecursivos(diretorio, [
|
|
1916
|
+
".sql", ".psql", ".ddl", ".prisma",
|
|
1917
|
+
".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
|
|
1918
|
+
".py", ".dart", ".cs", ".java", ".go", ".rs", ".cpp", ".cc", ".cxx", ".hpp", ".h",
|
|
1919
|
+
]);
|
|
1761
1920
|
for (const arquivo of arquivos) {
|
|
1762
1921
|
const codigo = await readFile(arquivo, "utf8");
|
|
1763
1922
|
const extracoes = arquivo.endsWith(".prisma")
|
|
@@ -1771,33 +1930,35 @@ async function indexarPersistenciaDeclarativa(diretorios) {
|
|
|
1771
1930
|
return { recursos: [...recursos.values()] };
|
|
1772
1931
|
}
|
|
1773
1932
|
function normalizarOrigemParaEngine(origem) {
|
|
1774
|
-
return origem && origem !== "firebase" ? origem : undefined;
|
|
1933
|
+
return origem && origem !== "firebase" && origem !== "arquivo" ? origem : undefined;
|
|
1775
1934
|
}
|
|
1776
|
-
function registrarColunaPersistenciaDrift(colunas,
|
|
1935
|
+
function registrarColunaPersistenciaDrift(colunas, origem, recurso, coluna, arquivo) {
|
|
1777
1936
|
const recursoNormalizado = fecharPrefixoRecurso(limparLiteralRecurso(recurso));
|
|
1778
1937
|
const colunaNormalizada = fecharPrefixoRecurso(limparLiteralRecurso(coluna));
|
|
1779
1938
|
if (!recursoNormalizado || !colunaNormalizada || recursoEhIgnorado(colunaNormalizada)) {
|
|
1780
1939
|
return;
|
|
1781
1940
|
}
|
|
1782
|
-
const chave = `${
|
|
1941
|
+
const chave = `${origem}:${normalizarNomeRecursoDrift(recursoNormalizado)}:${normalizarNomeRecursoDrift(colunaNormalizada)}:${arquivo}`;
|
|
1783
1942
|
if (!colunas.has(chave)) {
|
|
1784
1943
|
colunas.set(chave, {
|
|
1785
|
-
|
|
1944
|
+
origem,
|
|
1945
|
+
categoriaPersistencia: categorizarPersistenciaPorOrigem(origem),
|
|
1786
1946
|
recurso: recursoNormalizado,
|
|
1787
1947
|
coluna: colunaNormalizada,
|
|
1788
1948
|
arquivo,
|
|
1789
1949
|
});
|
|
1790
1950
|
}
|
|
1791
1951
|
}
|
|
1792
|
-
function registrarRepositorioPersistenciaDrift(repositorios,
|
|
1952
|
+
function registrarRepositorioPersistenciaDrift(repositorios, origem, recurso, arquivo) {
|
|
1793
1953
|
const recursoNormalizado = fecharPrefixoRecurso(limparLiteralRecurso(recurso));
|
|
1794
1954
|
if (!recursoNormalizado) {
|
|
1795
1955
|
return;
|
|
1796
1956
|
}
|
|
1797
|
-
const chave = `${
|
|
1957
|
+
const chave = `${origem}:${normalizarNomeRecursoDrift(recursoNormalizado)}:${arquivo}`;
|
|
1798
1958
|
if (!repositorios.has(chave)) {
|
|
1799
1959
|
repositorios.set(chave, {
|
|
1800
|
-
|
|
1960
|
+
origem,
|
|
1961
|
+
categoriaPersistencia: categorizarPersistenciaPorOrigem(origem),
|
|
1801
1962
|
recurso: recursoNormalizado,
|
|
1802
1963
|
arquivo,
|
|
1803
1964
|
});
|
|
@@ -1912,19 +2073,38 @@ function extrairCamposRedisDetalhados(arquivo, codigo) {
|
|
|
1912
2073
|
}
|
|
1913
2074
|
return [...colunas.values()];
|
|
1914
2075
|
}
|
|
2076
|
+
function extrairCamposArquivoLocalDetalhados(arquivo, codigo) {
|
|
2077
|
+
const colunas = new Map();
|
|
2078
|
+
const recursos = extrairRecursosArquivoLocal(arquivo, codigo);
|
|
2079
|
+
if (recursos.length === 0) {
|
|
2080
|
+
return [];
|
|
2081
|
+
}
|
|
2082
|
+
const registrarCampo = (recurso, trecho) => {
|
|
2083
|
+
for (const match of trecho.matchAll(/["'`]?([A-Za-z_][\w$-]*)["'`]?\s*:/g)) {
|
|
2084
|
+
registrarColunaPersistenciaDrift(colunas, "arquivo", recurso, match[1], arquivo);
|
|
2085
|
+
}
|
|
2086
|
+
};
|
|
2087
|
+
const blocos = [
|
|
2088
|
+
...codigo.matchAll(/\b(?:_empty_store|empty_store|default_store)\b[\s\S]{0,1000}?return\s*\{([\s\S]*?)\n\s*\}/g),
|
|
2089
|
+
...codigo.matchAll(/\bstore\s*=\s*\{([\s\S]*?)\n\s*\}/g),
|
|
2090
|
+
];
|
|
2091
|
+
for (const recurso of recursos) {
|
|
2092
|
+
for (const bloco of blocos) {
|
|
2093
|
+
registrarCampo(recurso.nome, bloco[1] ?? "");
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
return [...colunas.values()];
|
|
2097
|
+
}
|
|
1915
2098
|
function registrarRepositoriosPorRecursos(repositorios, arquivo, codigo, recursos) {
|
|
1916
2099
|
const contextoRepositorio = /(?:repository|repositories|repositorio|repositorios|repo|dao|store|queries|persistence|persistencia)/i.test(arquivo)
|
|
1917
2100
|
|| /\b(?:Repository|Repositories|Dao|Store)\b/.test(codigo);
|
|
1918
|
-
const contextoAcesso = /\b(?:select|insert|update|delete|aggregate|findOne|findMany|findUnique|findFirst|prisma\.|db\.collection|createClient|hset|hget|xadd|xread)\b/i.test(codigo)
|
|
2101
|
+
const contextoAcesso = /\b(?:select|insert|update|delete|aggregate|findOne|findMany|findUnique|findFirst|prisma\.|db\.collection|createClient|hset|hget|xadd|xread|json\.(?:load|loads|dump|dumps)|JSON\.(?:parse|stringify)|read_text|write_text|readFile(?:Sync)?|writeFile(?:Sync)?|open)\b/i.test(codigo)
|
|
2102
|
+
|| /\.(?:json|jsonl|ndjson|db|sqlite|sqlite3)\b/i.test(codigo);
|
|
1919
2103
|
if (!contextoRepositorio && !contextoAcesso) {
|
|
1920
2104
|
return;
|
|
1921
2105
|
}
|
|
1922
2106
|
for (const recurso of recursos) {
|
|
1923
|
-
|
|
1924
|
-
if (!engine) {
|
|
1925
|
-
continue;
|
|
1926
|
-
}
|
|
1927
|
-
registrarRepositorioPersistenciaDrift(repositorios, engine, recurso.nome, arquivo);
|
|
2107
|
+
registrarRepositorioPersistenciaDrift(repositorios, recurso.origem, recurso.nome, arquivo);
|
|
1928
2108
|
}
|
|
1929
2109
|
}
|
|
1930
2110
|
async function indexarPersistenciaDetalhada(diretorios) {
|
|
@@ -1942,16 +2122,19 @@ async function indexarPersistenciaDetalhada(diretorios) {
|
|
|
1942
2122
|
? extrairRecursosPrisma(arquivo, codigo)
|
|
1943
2123
|
: extrairRecursosPersistenciaCodigoVivo(arquivo, codigo);
|
|
1944
2124
|
for (const coluna of extrairColunasSqlDetalhadas(arquivo, codigo)) {
|
|
1945
|
-
registrarColunaPersistenciaDrift(colunas, coluna.
|
|
2125
|
+
registrarColunaPersistenciaDrift(colunas, coluna.origem, coluna.recurso, coluna.coluna, coluna.arquivo);
|
|
1946
2126
|
}
|
|
1947
2127
|
for (const coluna of extrairColunasPrismaDetalhadas(arquivo, codigo)) {
|
|
1948
|
-
registrarColunaPersistenciaDrift(colunas, coluna.
|
|
2128
|
+
registrarColunaPersistenciaDrift(colunas, coluna.origem, coluna.recurso, coluna.coluna, coluna.arquivo);
|
|
1949
2129
|
}
|
|
1950
2130
|
for (const coluna of extrairCamposMongoDetalhados(arquivo, codigo)) {
|
|
1951
|
-
registrarColunaPersistenciaDrift(colunas, coluna.
|
|
2131
|
+
registrarColunaPersistenciaDrift(colunas, coluna.origem, coluna.recurso, coluna.coluna, coluna.arquivo);
|
|
1952
2132
|
}
|
|
1953
2133
|
for (const coluna of extrairCamposRedisDetalhados(arquivo, codigo)) {
|
|
1954
|
-
registrarColunaPersistenciaDrift(colunas, coluna.
|
|
2134
|
+
registrarColunaPersistenciaDrift(colunas, coluna.origem, coluna.recurso, coluna.coluna, coluna.arquivo);
|
|
2135
|
+
}
|
|
2136
|
+
for (const coluna of extrairCamposArquivoLocalDetalhados(arquivo, codigo)) {
|
|
2137
|
+
registrarColunaPersistenciaDrift(colunas, coluna.origem, coluna.recurso, coluna.coluna, coluna.arquivo);
|
|
1955
2138
|
}
|
|
1956
2139
|
registrarRepositoriosPorRecursos(repositorios, arquivo, codigo, recursos);
|
|
1957
2140
|
}
|
|
@@ -1964,12 +2147,70 @@ async function indexarPersistenciaDetalhada(diretorios) {
|
|
|
1964
2147
|
function recursoDetalhadoCombina(recurso, esperado) {
|
|
1965
2148
|
return variantesNomeRecursoDrift(recurso).some((variante) => esperado.nomes.some((nome) => variantesNomeRecursoDrift(nome).includes(variante)));
|
|
1966
2149
|
}
|
|
2150
|
+
function deduplicarRecursosResolvidos(recursos) {
|
|
2151
|
+
return [...new Map(recursos.map((recurso) => [`${recurso.origem}:${recurso.tipo}:${recurso.nome}:${recurso.arquivo}:${recurso.simbolo ?? ""}`, recurso])).values()];
|
|
2152
|
+
}
|
|
2153
|
+
function normalizarArquivoDeclaradoDrift(valor) {
|
|
2154
|
+
return normalizarFragmentoArquivo(valor);
|
|
2155
|
+
}
|
|
2156
|
+
function arquivoCombinaDeclaradoDrift(arquivoReal, arquivoDeclarado) {
|
|
2157
|
+
const real = normalizarArquivoDeclaradoDrift(arquivoReal);
|
|
2158
|
+
const declarado = normalizarArquivoDeclaradoDrift(arquivoDeclarado);
|
|
2159
|
+
return real === declarado || real.endsWith(declarado) || declarado.endsWith(real);
|
|
2160
|
+
}
|
|
2161
|
+
function coletarArquivosPreferidosPersistenciaTask(task) {
|
|
2162
|
+
const arquivos = new Set();
|
|
2163
|
+
for (const vinculo of task.vinculos) {
|
|
2164
|
+
if (vinculo.arquivo) {
|
|
2165
|
+
arquivos.add(vinculo.arquivo);
|
|
2166
|
+
}
|
|
2167
|
+
if (vinculo.tipo === "arquivo" && vinculo.valor) {
|
|
2168
|
+
arquivos.add(vinculo.valor);
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
return arquivos;
|
|
2172
|
+
}
|
|
2173
|
+
function resolverPersistenciaLocalPorTask(mapaRecursos, task, ir, esperado) {
|
|
2174
|
+
const todosRecursos = deduplicarRecursosResolvidos([...mapaRecursos.values()].flat());
|
|
2175
|
+
const arquivosPreferidos = [...coletarArquivosPreferidosPersistenciaTask(task)];
|
|
2176
|
+
const candidatosPorArquivo = arquivosPreferidos.length > 0
|
|
2177
|
+
? todosRecursos.filter((recurso) => recurso.origem === "arquivo"
|
|
2178
|
+
&& arquivosPreferidos.some((arquivo) => arquivoCombinaDeclaradoDrift(recurso.arquivo, arquivo)))
|
|
2179
|
+
: [];
|
|
2180
|
+
if (candidatosPorArquivo.length > 0) {
|
|
2181
|
+
return candidatosPorArquivo;
|
|
2182
|
+
}
|
|
2183
|
+
const termos = new Set([
|
|
2184
|
+
...quebrarTermosEscopo(ir.nome),
|
|
2185
|
+
...quebrarTermosEscopo(task.nome),
|
|
2186
|
+
...quebrarTermosEscopo(esperado.alvo),
|
|
2187
|
+
]);
|
|
2188
|
+
if (termos.size === 0) {
|
|
2189
|
+
return [];
|
|
2190
|
+
}
|
|
2191
|
+
return todosRecursos.filter((recurso) => recurso.origem === "arquivo"
|
|
2192
|
+
&& [...termos].some((termo) => variantesNomeRecursoDrift(recurso.nome).some((variacao) => variacao.includes(termo))));
|
|
2193
|
+
}
|
|
2194
|
+
function detalhePersistenciaCombinaOrigem(origemDetalhe, recursoReal) {
|
|
2195
|
+
if (!recursoReal) {
|
|
2196
|
+
return true;
|
|
2197
|
+
}
|
|
2198
|
+
return origemDetalhe === recursoReal.origem;
|
|
2199
|
+
}
|
|
1967
2200
|
function localizarCompatibilidadePersistencia(bancos, esperado, recursoReal) {
|
|
1968
2201
|
for (const banco of bancos) {
|
|
1969
2202
|
for (const recurso of banco.resources) {
|
|
1970
2203
|
if (!recursoPersistenciaCombinaAlvo(recurso, esperado.alvo)) {
|
|
1971
2204
|
continue;
|
|
1972
2205
|
}
|
|
2206
|
+
if (recursoReal?.origem === "arquivo") {
|
|
2207
|
+
return {
|
|
2208
|
+
engine: "arquivo",
|
|
2209
|
+
compatibilidade: "adaptado",
|
|
2210
|
+
motivoCompatibilidade: `Persistencia local/arquivo detectada no codigo vivo em vez do engine ${banco.engine}.`,
|
|
2211
|
+
tipo: recursoReal.tipo,
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
1973
2214
|
const engine = banco.engine ?? normalizarOrigemParaEngine(recursoReal?.origem);
|
|
1974
2215
|
const compatibilidade = engine
|
|
1975
2216
|
? recurso.compatibilidade.find((item) => item.engine === engine) ?? recurso.compatibilidade[0]
|
|
@@ -1982,6 +2223,14 @@ function localizarCompatibilidadePersistencia(bancos, esperado, recursoReal) {
|
|
|
1982
2223
|
};
|
|
1983
2224
|
}
|
|
1984
2225
|
}
|
|
2226
|
+
if (recursoReal?.origem === "arquivo") {
|
|
2227
|
+
return {
|
|
2228
|
+
engine: "arquivo",
|
|
2229
|
+
compatibilidade: "desconhecida",
|
|
2230
|
+
motivoCompatibilidade: "Persistencia local/arquivo detectada sem database vendor-first declarado.",
|
|
2231
|
+
tipo: recursoReal.tipo,
|
|
2232
|
+
};
|
|
2233
|
+
}
|
|
1985
2234
|
return {
|
|
1986
2235
|
engine: recursoReal?.origem ?? esperado.origem ?? "desconhecido",
|
|
1987
2236
|
compatibilidade: "desconhecida",
|
|
@@ -2002,22 +2251,41 @@ export async function analisarPersistenciaReal(contexto, mapaRecursos, detalhesP
|
|
|
2002
2251
|
for (const task of ir.tasks) {
|
|
2003
2252
|
for (const esperado of extrairRecursosEsperados(task, ir)) {
|
|
2004
2253
|
const correspondencias = esperado.nomes.flatMap((nome) => variantesNomeRecursoDrift(nome).flatMap((variante) => (mapa.get(variante) ?? []).filter((recurso) => recursoResolvidoCombinaEsperado(recurso, esperado))));
|
|
2005
|
-
|
|
2254
|
+
let recursosReais = deduplicarRecursosResolvidos(correspondencias);
|
|
2255
|
+
const arquivosPreferidos = [...coletarArquivosPreferidosPersistenciaTask(task)];
|
|
2256
|
+
if (recursosReais.length === 0) {
|
|
2257
|
+
recursosReais = resolverPersistenciaLocalPorTask(mapa, task, ir, esperado);
|
|
2258
|
+
}
|
|
2006
2259
|
const compatibilidade = localizarCompatibilidadePersistencia(ir.databases, esperado, recursosReais[0]);
|
|
2007
|
-
|
|
2008
|
-
.filter((coluna) => (
|
|
2260
|
+
let colunas = [...new Set(detalhes.colunas
|
|
2261
|
+
.filter((coluna) => detalhePersistenciaCombinaOrigem(coluna.origem, recursosReais[0])
|
|
2009
2262
|
&& recursoDetalhadoCombina(coluna.recurso, esperado))
|
|
2010
2263
|
.map((coluna) => coluna.coluna))].sort((a, b) => a.localeCompare(b, "pt-BR"));
|
|
2011
|
-
|
|
2012
|
-
.filter((repositorio) => (
|
|
2264
|
+
let repositorios = [...new Set(detalhes.repositorios
|
|
2265
|
+
.filter((repositorio) => detalhePersistenciaCombinaOrigem(repositorio.origem, recursosReais[0])
|
|
2013
2266
|
&& recursoDetalhadoCombina(repositorio.recurso, esperado))
|
|
2014
2267
|
.map((repositorio) => repositorio.arquivo))].sort((a, b) => a.localeCompare(b, "pt-BR"));
|
|
2268
|
+
if (recursosReais.some((recurso) => recurso.origem === "arquivo") && arquivosPreferidos.length > 0) {
|
|
2269
|
+
if (colunas.length === 0) {
|
|
2270
|
+
colunas = [...new Set(detalhes.colunas
|
|
2271
|
+
.filter((coluna) => coluna.origem === "arquivo"
|
|
2272
|
+
&& arquivosPreferidos.some((arquivo) => arquivoCombinaDeclaradoDrift(coluna.arquivo, arquivo)))
|
|
2273
|
+
.map((coluna) => coluna.coluna))].sort((a, b) => a.localeCompare(b, "pt-BR"));
|
|
2274
|
+
}
|
|
2275
|
+
if (repositorios.length === 0) {
|
|
2276
|
+
repositorios = [...new Set(detalhes.repositorios
|
|
2277
|
+
.filter((repositorio) => repositorio.origem === "arquivo"
|
|
2278
|
+
&& arquivosPreferidos.some((arquivo) => arquivoCombinaDeclaradoDrift(repositorio.arquivo, arquivo)))
|
|
2279
|
+
.map((repositorio) => repositorio.arquivo))].sort((a, b) => a.localeCompare(b, "pt-BR"));
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2015
2282
|
const arquivos = [...new Set(recursosReais.map((recurso) => recurso.arquivo))].sort((a, b) => a.localeCompare(b, "pt-BR"));
|
|
2016
2283
|
registros.push({
|
|
2017
2284
|
modulo: ir.nome,
|
|
2018
2285
|
task: task.nome,
|
|
2019
2286
|
alvo: esperado.alvo,
|
|
2020
2287
|
engine: compatibilidade.engine,
|
|
2288
|
+
categoriaPersistencia: categorizarPersistenciaPorOrigem((compatibilidade.engine === "desconhecido" ? undefined : compatibilidade.engine)),
|
|
2021
2289
|
tipo: compatibilidade.tipo,
|
|
2022
2290
|
status: recursosReais.length === 0
|
|
2023
2291
|
? "divergente"
|
|
@@ -2259,6 +2527,10 @@ function recursoPersistenciaCombinaAlvo(recurso, alvo) {
|
|
|
2259
2527
|
}
|
|
2260
2528
|
return nomesRecursoPersistencia(recurso).some((nome) => variantesNomeRecursoDrift(nome).some((variacao) => alvoVariantes.has(variacao)));
|
|
2261
2529
|
}
|
|
2530
|
+
function taskSugerePersistenciaSemBanco(task) {
|
|
2531
|
+
return task.vinculos.some((vinculo) => /(?:repository|repositories|repositorio|repo|store|storage|persist|cache)/i.test(`${vinculo.valor} ${vinculo.arquivo ?? ""} ${vinculo.simbolo ?? ""}`))
|
|
2532
|
+
|| task.implementacoesExternas.some((impl) => /(?:repository|repositories|repositorio|repo|store|storage|persist|cache)/i.test(impl.caminho));
|
|
2533
|
+
}
|
|
2262
2534
|
function extrairRecursosEsperados(task, ir) {
|
|
2263
2535
|
const esperados = new Map();
|
|
2264
2536
|
const registrar = (esperado) => {
|
|
@@ -2279,7 +2551,25 @@ function extrairRecursosEsperados(task, ir) {
|
|
|
2279
2551
|
}
|
|
2280
2552
|
}
|
|
2281
2553
|
const efeitosPersistencia = task.efeitosEstruturados.filter((efeito) => ["persistencia", "db.read", "db.write"].includes(efeito.categoria) && Boolean(efeito.alvo));
|
|
2282
|
-
if (efeitosPersistencia.length === 0
|
|
2554
|
+
if (efeitosPersistencia.length === 0) {
|
|
2555
|
+
return [...esperados.values()];
|
|
2556
|
+
}
|
|
2557
|
+
if (ir.databases.length === 0) {
|
|
2558
|
+
const sugerePersistenciaLocal = taskSugerePersistenciaSemBanco(task);
|
|
2559
|
+
for (const efeito of efeitosPersistencia) {
|
|
2560
|
+
if (efeito.categoria !== "persistencia" || !sugerePersistenciaLocal) {
|
|
2561
|
+
continue;
|
|
2562
|
+
}
|
|
2563
|
+
if ([...esperados.values()].some((item) => item.alvo === efeito.alvo)) {
|
|
2564
|
+
continue;
|
|
2565
|
+
}
|
|
2566
|
+
registrar({
|
|
2567
|
+
categoria: "persistencia",
|
|
2568
|
+
alvo: efeito.alvo,
|
|
2569
|
+
tiposAceitos: ["table", "collection", "document", "keyspace", "stream", "view", "query", "index", "arquivo_local"],
|
|
2570
|
+
nomes: [efeito.alvo],
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
2283
2573
|
return [...esperados.values()];
|
|
2284
2574
|
}
|
|
2285
2575
|
for (const efeito of efeitosPersistencia) {
|
|
@@ -2570,7 +2860,10 @@ export async function analisarDriftLegado(contexto, opcoes) {
|
|
|
2570
2860
|
checksSugeridos: [],
|
|
2571
2861
|
});
|
|
2572
2862
|
for (const recursoEsperado of extrairRecursosEsperados(task, ir)) {
|
|
2573
|
-
|
|
2863
|
+
let resolvido = resolverRecursoEsperado(mapaRecursos, recursoEsperado, arquivosReferenciados);
|
|
2864
|
+
if (!resolvido) {
|
|
2865
|
+
resolvido = resolverPersistenciaLocalPorTask(mapaRecursos, task, ir, recursoEsperado)[0];
|
|
2866
|
+
}
|
|
2574
2867
|
const registro = {
|
|
2575
2868
|
modulo: ir.nome,
|
|
2576
2869
|
task: task.nome,
|
|
@@ -2903,6 +3196,7 @@ export async function analisarDriftLegado(contexto, opcoes) {
|
|
|
2903
3196
|
});
|
|
2904
3197
|
}
|
|
2905
3198
|
}
|
|
3199
|
+
const relevanciaConsumer = construirContextoRelevanciaConsumer(contexto, tasksResumo, vinculosValidos);
|
|
2906
3200
|
const consumersFiltrados = filtrarConsumerSurfacesPorEscopo([...indexTs.consumerSurfaces, ...indexDart.consumerSurfaces].sort((a, b) => a.rota.localeCompare(b.rota, "pt-BR")
|
|
2907
3201
|
|| a.tipoArquivo.localeCompare(b.tipoArquivo, "pt-BR")
|
|
2908
3202
|
|| a.arquivo.localeCompare(b.arquivo, "pt-BR")), [...new Map([...indexTs.simbolos, ...indexDart.simbolos]
|
|
@@ -2915,7 +3209,7 @@ export async function analisarDriftLegado(contexto, opcoes) {
|
|
|
2915
3209
|
simbolo: simbolo.simbolo,
|
|
2916
3210
|
},
|
|
2917
3211
|
])).values()].sort((a, b) => a.caminho.localeCompare(b.caminho, "pt-BR")
|
|
2918
|
-
|| a.arquivo.localeCompare(b.arquivo, "pt-BR")), contexto, configuracaoEscopo);
|
|
3212
|
+
|| a.arquivo.localeCompare(b.arquivo, "pt-BR")), contexto, configuracaoEscopo, relevanciaConsumer);
|
|
2919
3213
|
const consumerSurfaces = consumersFiltrados.consumerSurfaces;
|
|
2920
3214
|
const consumerBridges = consumersFiltrados.consumerBridges;
|
|
2921
3215
|
const appRoutes = [...new Set(consumerSurfaces.map((surface) => surface.rota))]
|