@semacode/cli 1.3.7 → 1.5.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.
Files changed (47) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +61 -41
  3. package/dist/drift.d.ts +98 -4
  4. package/dist/drift.js +1881 -546
  5. package/dist/drift.js.map +1 -1
  6. package/dist/importador.d.ts +2 -0
  7. package/dist/importador.js +166 -7
  8. package/dist/importador.js.map +1 -1
  9. package/dist/index.js +108 -9
  10. package/dist/index.js.map +1 -1
  11. package/dist/projeto.d.ts +6 -1
  12. package/dist/projeto.js.map +1 -1
  13. package/docs/cli.md +101 -0
  14. package/docs/instalacao-e-primeiro-uso.md +109 -196
  15. package/docs/integracao-com-ia.md +47 -198
  16. package/docs/persistencia-vendor-first.md +145 -0
  17. package/docs/sintaxe.md +67 -251
  18. package/exemplos/persistencia_vendor_first.sema +86 -0
  19. package/node_modules/@sema/gerador-css/package.json +1 -1
  20. package/node_modules/@sema/gerador-dart/package.json +1 -1
  21. package/node_modules/@sema/gerador-html/package.json +1 -1
  22. package/node_modules/@sema/gerador-javascript/package.json +1 -1
  23. package/node_modules/@sema/gerador-lua/package.json +1 -1
  24. package/node_modules/@sema/gerador-python/package.json +1 -1
  25. package/node_modules/@sema/gerador-typescript/package.json +1 -1
  26. package/node_modules/@sema/nucleo/dist/ast/tipos.d.ts +2 -1
  27. package/node_modules/@sema/nucleo/dist/formatador/index.js +32 -17
  28. package/node_modules/@sema/nucleo/dist/formatador/index.js.map +1 -1
  29. package/node_modules/@sema/nucleo/dist/index.d.ts +1 -0
  30. package/node_modules/@sema/nucleo/dist/index.js +1 -0
  31. package/node_modules/@sema/nucleo/dist/index.js.map +1 -1
  32. package/node_modules/@sema/nucleo/dist/ir/conversor.js +94 -0
  33. package/node_modules/@sema/nucleo/dist/ir/conversor.js.map +1 -1
  34. package/node_modules/@sema/nucleo/dist/ir/modelos.d.ts +60 -0
  35. package/node_modules/@sema/nucleo/dist/lexer/tokens.js +15 -0
  36. package/node_modules/@sema/nucleo/dist/lexer/tokens.js.map +1 -1
  37. package/node_modules/@sema/nucleo/dist/parser/parser.js +98 -3
  38. package/node_modules/@sema/nucleo/dist/parser/parser.js.map +1 -1
  39. package/node_modules/@sema/nucleo/dist/persistencia/contratos.d.ts +39 -0
  40. package/node_modules/@sema/nucleo/dist/persistencia/contratos.js +294 -0
  41. package/node_modules/@sema/nucleo/dist/persistencia/contratos.js.map +1 -0
  42. package/node_modules/@sema/nucleo/dist/semantico/analisador.d.ts +1 -1
  43. package/node_modules/@sema/nucleo/dist/semantico/analisador.js +118 -2
  44. package/node_modules/@sema/nucleo/dist/semantico/analisador.js.map +1 -1
  45. package/node_modules/@sema/nucleo/package.json +1 -1
  46. package/node_modules/@sema/padroes/package.json +1 -1
  47. package/package.json +11 -11
package/dist/drift.js CHANGED
@@ -8,7 +8,7 @@ import { extrairRotasJava, extrairSimbolosJava } from "./java-http.js";
8
8
  import { contarIndentacaoPython, extrairRotasFlaskDecoradas, normalizarCaminhoFlask } from "./python-http.js";
9
9
  import { extrairRotasRust, extrairSimbolosRust } from "./rust-http.js";
10
10
  import { extrairRotasTypeScriptHttp } from "./typescript-http.js";
11
- const DIRETORIOS_IGNORADOS = new Set([
11
+ const DIRETORIOS_IGNORADOS_BASE = new Set([
12
12
  ".git",
13
13
  ".hg",
14
14
  ".svn",
@@ -25,9 +25,563 @@ const DIRETORIOS_IGNORADOS = new Set([
25
25
  ".tmp",
26
26
  "generated",
27
27
  ]);
28
+ const DIRETORIOS_WORKTREE = [
29
+ ".claude",
30
+ "worktrees",
31
+ ];
32
+ const DIRETORIOS_CONSUMIDOR_LATERAL = [
33
+ "showcase",
34
+ "showcases",
35
+ "storybook",
36
+ "stories",
37
+ "playground",
38
+ "sandbox",
39
+ "fixture",
40
+ "fixtures",
41
+ "demo",
42
+ "demos",
43
+ "sample",
44
+ "samples",
45
+ "mini-web",
46
+ ];
47
+ const TERMOS_ESCopo_IGNORADOS = new Set([
48
+ "api",
49
+ "app",
50
+ "apps",
51
+ "base",
52
+ "codigo",
53
+ "config",
54
+ "controller",
55
+ "controllers",
56
+ "data",
57
+ "drift",
58
+ "flow",
59
+ "module",
60
+ "modulo",
61
+ "publico",
62
+ "route",
63
+ "routes",
64
+ "schema",
65
+ "sema",
66
+ "service",
67
+ "services",
68
+ "src",
69
+ "task",
70
+ "tasks",
71
+ "tests",
72
+ "ui",
73
+ "web",
74
+ ]);
75
+ let diretoriosIgnoradosAtivos = new Set(DIRETORIOS_IGNORADOS_BASE);
28
76
  function normalizarFragmentoArquivo(valor) {
29
77
  return valor.replace(/\\/g, "/").replace(/^\.?\//, "").trim().toLowerCase();
30
78
  }
79
+ function normalizarEscopoDrift(valor) {
80
+ if (valor === "arquivo" || valor === "modulo" || valor === "projeto") {
81
+ return valor;
82
+ }
83
+ return "modulo";
84
+ }
85
+ function resolverOpcoesDrift(opcoes) {
86
+ return {
87
+ escopo: normalizarEscopoDrift(opcoes?.escopo),
88
+ ignorarWorktrees: opcoes?.ignorarWorktrees !== false,
89
+ ignorarConsumidoresLaterais: opcoes?.ignorarConsumidoresLaterais !== false,
90
+ };
91
+ }
92
+ function resolverDiretoriosIgnoradosAtivos(opcoes) {
93
+ const resolvidas = resolverOpcoesDrift(opcoes);
94
+ const diretorios = new Set(DIRETORIOS_IGNORADOS_BASE);
95
+ if (resolvidas.ignorarWorktrees) {
96
+ for (const diretorio of DIRETORIOS_WORKTREE) {
97
+ diretorios.add(diretorio);
98
+ }
99
+ }
100
+ if (resolvidas.ignorarConsumidoresLaterais) {
101
+ for (const diretorio of DIRETORIOS_CONSUMIDOR_LATERAL) {
102
+ diretorios.add(diretorio);
103
+ }
104
+ }
105
+ return diretorios;
106
+ }
107
+ function quebrarTermosEscopo(valor) {
108
+ return paraIdentificadorModulo(valor)
109
+ .split("_")
110
+ .map((item) => item.trim())
111
+ .filter((item) => item.length >= 3 && !TERMOS_ESCopo_IGNORADOS.has(item));
112
+ }
113
+ function extrairTermosEscopoDrift(contexto, escopo) {
114
+ if (escopo === "projeto") {
115
+ return [];
116
+ }
117
+ const termos = new Set();
118
+ termos.add(paraIdentificadorModulo(path.basename(contexto.entradaResolvida, path.extname(contexto.entradaResolvida))));
119
+ for (const modulo of contexto.modulosSelecionados) {
120
+ const ir = modulo.resultado.ir;
121
+ if (!ir) {
122
+ continue;
123
+ }
124
+ for (const termo of quebrarTermosEscopo(ir.nome)) {
125
+ termos.add(termo);
126
+ }
127
+ for (const task of ir.tasks) {
128
+ for (const termo of quebrarTermosEscopo(task.nome)) {
129
+ termos.add(termo);
130
+ }
131
+ }
132
+ for (const route of ir.routes) {
133
+ for (const termo of quebrarTermosEscopo(route.nome)) {
134
+ termos.add(termo);
135
+ }
136
+ if (route.caminho) {
137
+ for (const termo of quebrarTermosEscopo(route.caminho)) {
138
+ termos.add(termo);
139
+ }
140
+ }
141
+ }
142
+ }
143
+ return [...termos].filter(Boolean).sort((a, b) => a.localeCompare(b, "pt-BR"));
144
+ }
145
+ function caminhoTemSegmentoIgnorado(arquivo, segmentos) {
146
+ const partes = normalizarFragmentoArquivo(arquivo).split("/").filter(Boolean);
147
+ return partes.some((parte) => segmentos.includes(parte));
148
+ }
149
+ function normalizarCaminhoComparacao(caminhoArquivo) {
150
+ return path.resolve(caminhoArquivo).replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
151
+ }
152
+ function caminhoEstaDentroDe(base, alvo) {
153
+ const baseNormalizada = normalizarCaminhoComparacao(base);
154
+ const alvoNormalizado = normalizarCaminhoComparacao(alvo);
155
+ return alvoNormalizado === baseNormalizada || alvoNormalizado.startsWith(`${baseNormalizada}/`);
156
+ }
157
+ function resolverRaizEscopoReal(contexto) {
158
+ const entrada = path.resolve(contexto.entradaResolvida);
159
+ return path.extname(entrada) ? path.dirname(entrada) : entrada;
160
+ }
161
+ function resolverRaizesExplicitasConfiguradas(contexto) {
162
+ const configCarregada = contexto.configCarregada;
163
+ if (!configCarregada) {
164
+ return [];
165
+ }
166
+ const origensDeclaradas = configCarregada.config.origens ?? (configCarregada.config.origem ? [configCarregada.config.origem] : []);
167
+ return [...new Set([
168
+ ...(configCarregada.config.diretoriosCodigo ?? []).map((diretorio) => path.resolve(configCarregada.baseDiretorio, diretorio)),
169
+ ...origensDeclaradas.map((origem) => path.resolve(configCarregada.baseDiretorio, origem)),
170
+ ])].sort((a, b) => a.localeCompare(b, "pt-BR"));
171
+ }
172
+ function resolverRaizesIgnoradasPermitidas(contexto, segmentosIgnorados) {
173
+ return [...new Set([
174
+ resolverRaizEscopoReal(contexto),
175
+ ...resolverRaizesExplicitasConfiguradas(contexto),
176
+ ])]
177
+ .filter((raiz) => caminhoTemSegmentoIgnorado(raiz, segmentosIgnorados))
178
+ .sort((a, b) => a.localeCompare(b, "pt-BR"));
179
+ }
180
+ function caminhoIgnoradoForaDoEscopoReal(caminhoArquivo, segmentosIgnorados, raizesPermitidas) {
181
+ if (!caminhoTemSegmentoIgnorado(caminhoArquivo, segmentosIgnorados)) {
182
+ return false;
183
+ }
184
+ if (raizesPermitidas.length === 0) {
185
+ return true;
186
+ }
187
+ return !raizesPermitidas.some((raiz) => caminhoEstaDentroDe(raiz, caminhoArquivo));
188
+ }
189
+ function filtrarCaminhosEscopoReal(caminhos, contexto, configuracao) {
190
+ const raizesWorktreePermitidas = resolverRaizesIgnoradasPermitidas(contexto, DIRETORIOS_WORKTREE);
191
+ const raizesConsumidorPermitidas = resolverRaizesIgnoradasPermitidas(contexto, DIRETORIOS_CONSUMIDOR_LATERAL);
192
+ return caminhos.filter((caminho) => {
193
+ if (configuracao.ignorarWorktrees && caminhoIgnoradoForaDoEscopoReal(caminho, DIRETORIOS_WORKTREE, raizesWorktreePermitidas)) {
194
+ return false;
195
+ }
196
+ if (configuracao.ignorarConsumidoresLaterais
197
+ && caminhoIgnoradoForaDoEscopoReal(caminho, DIRETORIOS_CONSUMIDOR_LATERAL, raizesConsumidorPermitidas)) {
198
+ return false;
199
+ }
200
+ return true;
201
+ });
202
+ }
203
+ function resolverDiretoriosCodigoEscopoReal(contexto, configuracao) {
204
+ return filtrarCaminhosEscopoReal(contexto.diretoriosCodigo, contexto, configuracao);
205
+ }
206
+ function textoCombinaEscopo(texto, termos) {
207
+ if (termos.length === 0) {
208
+ return true;
209
+ }
210
+ const normalizado = paraIdentificadorModulo(texto);
211
+ return termos.some((termo) => normalizado.includes(termo));
212
+ }
213
+ function filtrarConsumerSurfacesPorEscopo(consumerSurfaces, consumerBridges, contexto, configuracao) {
214
+ const raizesWorktreePermitidas = resolverRaizesIgnoradasPermitidas(contexto, DIRETORIOS_WORKTREE);
215
+ const raizesConsumidorPermitidas = resolverRaizesIgnoradasPermitidas(contexto, DIRETORIOS_CONSUMIDOR_LATERAL);
216
+ const manterSurface = (surface) => {
217
+ if (configuracao.ignorarWorktrees
218
+ && caminhoIgnoradoForaDoEscopoReal(surface.arquivo, DIRETORIOS_WORKTREE, raizesWorktreePermitidas)) {
219
+ return false;
220
+ }
221
+ if (configuracao.ignorarConsumidoresLaterais
222
+ && caminhoIgnoradoForaDoEscopoReal(surface.arquivo, DIRETORIOS_CONSUMIDOR_LATERAL, raizesConsumidorPermitidas)) {
223
+ return false;
224
+ }
225
+ const combinaEscopo = textoCombinaEscopo(`${surface.rota} ${surface.arquivo} ${surface.tipoArquivo}`, configuracao.termosEscopo);
226
+ if (!configuracao.ignorarConsumidoresLaterais) {
227
+ return combinaEscopo || configuracao.escopo === "projeto";
228
+ }
229
+ return configuracao.escopo === "projeto" ? true : combinaEscopo;
230
+ };
231
+ const manterBridge = (bridge) => {
232
+ if (configuracao.ignorarWorktrees
233
+ && caminhoIgnoradoForaDoEscopoReal(bridge.arquivo, DIRETORIOS_WORKTREE, raizesWorktreePermitidas)) {
234
+ return false;
235
+ }
236
+ if (configuracao.ignorarConsumidoresLaterais
237
+ && caminhoIgnoradoForaDoEscopoReal(bridge.arquivo, DIRETORIOS_CONSUMIDOR_LATERAL, raizesConsumidorPermitidas)) {
238
+ return false;
239
+ }
240
+ const combinaEscopo = textoCombinaEscopo(`${bridge.caminho} ${bridge.arquivo} ${bridge.simbolo}`, configuracao.termosEscopo);
241
+ if (!configuracao.ignorarConsumidoresLaterais) {
242
+ return combinaEscopo || configuracao.escopo === "projeto";
243
+ }
244
+ return configuracao.escopo === "projeto" ? true : combinaEscopo;
245
+ };
246
+ return {
247
+ consumerSurfaces: consumerSurfaces.filter(manterSurface),
248
+ consumerBridges: consumerBridges.filter(manterBridge),
249
+ };
250
+ }
251
+ const NOMES_RECURSO_IGNORADOS = new Set([
252
+ "all",
253
+ "and",
254
+ "as",
255
+ "by",
256
+ "create",
257
+ "delete",
258
+ "from",
259
+ "group",
260
+ "inner",
261
+ "into",
262
+ "join",
263
+ "left",
264
+ "limit",
265
+ "offset",
266
+ "on",
267
+ "or",
268
+ "order",
269
+ "outer",
270
+ "returning",
271
+ "right",
272
+ "select",
273
+ "set",
274
+ "table",
275
+ "update",
276
+ "values",
277
+ "view",
278
+ "where",
279
+ ]);
280
+ const OPERACOES_REDIS_KEYSPACE = [
281
+ "append",
282
+ "decr",
283
+ "del",
284
+ "expire",
285
+ "expireat",
286
+ "get",
287
+ "getdel",
288
+ "getex",
289
+ "getrange",
290
+ "hdel",
291
+ "hexists",
292
+ "hget",
293
+ "hgetall",
294
+ "hincrby",
295
+ "hkeys",
296
+ "hlen",
297
+ "hmget",
298
+ "hmset",
299
+ "hrandfield",
300
+ "hscan",
301
+ "hset",
302
+ "hsetnx",
303
+ "hvals",
304
+ "incr",
305
+ "incrby",
306
+ "lindex",
307
+ "llen",
308
+ "lpop",
309
+ "lpush",
310
+ "lrange",
311
+ "lrem",
312
+ "lset",
313
+ "rpop",
314
+ "rpush",
315
+ "sadd",
316
+ "scard",
317
+ "set",
318
+ "setex",
319
+ "setnx",
320
+ "smembers",
321
+ "spop",
322
+ "srem",
323
+ "ttl",
324
+ "type",
325
+ "zadd",
326
+ "zcard",
327
+ "zrange",
328
+ "zrem",
329
+ ];
330
+ const OPERACOES_REDIS_STREAM = [
331
+ "xadd",
332
+ "xdel",
333
+ "xgroupcreate",
334
+ "xgroupdestroy",
335
+ "xlen",
336
+ "xrange",
337
+ "xread",
338
+ "xreadgroup",
339
+ "xrevrange",
340
+ "xtrim",
341
+ ];
342
+ function limparLiteralRecurso(valor) {
343
+ return valor
344
+ .trim()
345
+ .replace(/^["'`]+|["'`]+$/g, "")
346
+ .replace(/\$\{[^}]+\}/g, "")
347
+ .replace(/\{[^}]+\}/g, "")
348
+ .replace(/%[sdifjo]/gi, "")
349
+ .trim();
350
+ }
351
+ function fecharPrefixoRecurso(valor) {
352
+ return valor.replace(/[:/_\-.]+$/g, "").trim();
353
+ }
354
+ function normalizarNomeRecursoDrift(valor) {
355
+ return fecharPrefixoRecurso(limparLiteralRecurso(valor))
356
+ .normalize("NFD")
357
+ .replace(/[\u0300-\u036f]/g, "")
358
+ .toLowerCase()
359
+ .replace(/["'`]/g, "")
360
+ .replace(/\s+/g, "");
361
+ }
362
+ function variantesNomeRecursoDrift(valor) {
363
+ const base = fecharPrefixoRecurso(limparLiteralRecurso(valor));
364
+ if (!base) {
365
+ return [];
366
+ }
367
+ const variantes = new Set();
368
+ const registrar = (candidato) => {
369
+ if (!candidato) {
370
+ return;
371
+ }
372
+ const normalizado = normalizarNomeRecursoDrift(candidato);
373
+ if (normalizado) {
374
+ variantes.add(normalizado);
375
+ }
376
+ };
377
+ registrar(base);
378
+ registrar(base.replace(/[.:/_-]+/g, "_"));
379
+ registrar(base.replace(/[.:/_-]+/g, ""));
380
+ const partes = base.split(/[.:/_-]+/).filter(Boolean);
381
+ if (partes.length > 1) {
382
+ registrar(partes.join("_"));
383
+ registrar(partes.join(""));
384
+ }
385
+ const singular = base.replace(/s$/i, "");
386
+ if (singular && singular !== base) {
387
+ registrar(singular);
388
+ }
389
+ else if (!/s$/i.test(base)) {
390
+ registrar(`${base}s`);
391
+ }
392
+ return [...variantes];
393
+ }
394
+ function recursoEhIgnorado(nome) {
395
+ const normalizado = normalizarNomeRecursoDrift(nome);
396
+ if (!normalizado || normalizado.length < 2) {
397
+ return true;
398
+ }
399
+ return NOMES_RECURSO_IGNORADOS.has(normalizado);
400
+ }
401
+ function registrarRecursoDrift(recursos, origem, tipo, nome, arquivo, simbolo) {
402
+ const nomeLimpo = fecharPrefixoRecurso(limparLiteralRecurso(nome));
403
+ if (!nomeLimpo || recursoEhIgnorado(nomeLimpo)) {
404
+ return;
405
+ }
406
+ const chave = `${origem}:${tipo}:${normalizarNomeRecursoDrift(nomeLimpo)}:${arquivo}:${simbolo ?? ""}`;
407
+ if (!recursos.has(chave)) {
408
+ recursos.set(chave, {
409
+ origem,
410
+ nome: nomeLimpo,
411
+ arquivo,
412
+ simbolo,
413
+ tipo,
414
+ });
415
+ }
416
+ }
417
+ function inferirMotoresRelacionais(codigo, arquivo) {
418
+ const motores = new Set();
419
+ const caminho = normalizarFragmentoArquivo(arquivo);
420
+ if (/\b(?:from|require)\s*\(?["'`]pg["'`]/i.test(codigo)
421
+ || /\bpostgres(?:ql)?\b/i.test(codigo)
422
+ || /\bon\s+conflict\b/i.test(codigo)
423
+ || /\breturning\b/i.test(codigo)
424
+ || /\bjsonb\b/i.test(codigo)
425
+ || /\bilike\b/i.test(codigo)
426
+ || /(?:^|\/)(?:postgres|pgsql)(?:\/|[-_.])/i.test(caminho)) {
427
+ motores.add("postgres");
428
+ }
429
+ if (/\b(?:from|require)\s*\(?["'`](?:mysql2?(?:\/promise)?|mysql)["'`]/i.test(codigo)
430
+ || /\bon\s+duplicate\s+key\b/i.test(codigo)
431
+ || /\bauto_increment\b/i.test(codigo)
432
+ || /\binnodb\b/i.test(codigo)
433
+ || /\bunsigned\b/i.test(codigo)
434
+ || /(?:^|\/)mysql(?:\/|[-_.])/i.test(caminho)) {
435
+ motores.add("mysql");
436
+ }
437
+ if (/\b(?:from|require)\s*\(?["'`](?:sqlite3|better-sqlite3|bun:sqlite|sqlite)["'`]/i.test(codigo)
438
+ || /\bpragma\b/i.test(codigo)
439
+ || /\bwithout\s+rowid\b/i.test(codigo)
440
+ || /\bsqlite\b/i.test(codigo)
441
+ || /(?:^|\/)sqlite(?:\/|[-_.])/i.test(caminho)) {
442
+ motores.add("sqlite");
443
+ }
444
+ const temSqlGenerico = /\b(?:select\b[\s\S]*?\bfrom\b|insert\s+into|update\s+[A-Za-z_][\w$.-]*\s+set|delete\s+from|create\s+(?:table|view)|alter\s+table|drop\s+(?:table|view)|join\s+[A-Za-z_][\w$.-]*)/i.test(codigo)
445
+ || /\.(?:from|into|table)\s*\(\s*["'`]/i.test(codigo)
446
+ || /\b(?:knex|db|trx)\s*\(\s*["'`][A-Za-z_][^"'`]+["'`]\s*\)/i.test(codigo)
447
+ || /\bprisma\.[A-Za-z_]\w*\.(?:find\w+|create|update|delete|upsert|aggregate|count)\b/i.test(codigo);
448
+ if (temSqlGenerico && motores.size === 0) {
449
+ motores.add("postgres");
450
+ motores.add("mysql");
451
+ motores.add("sqlite");
452
+ }
453
+ return [...motores];
454
+ }
455
+ function extrairRecursosSql(arquivo, codigo) {
456
+ const recursos = new Map();
457
+ const motores = inferirMotoresRelacionais(codigo, arquivo);
458
+ if (motores.length === 0) {
459
+ return [];
460
+ }
461
+ const registrarParaMotores = (tipo, nome) => {
462
+ for (const motor of motores) {
463
+ registrarRecursoDrift(recursos, motor, tipo, nome, arquivo);
464
+ }
465
+ };
466
+ const registrarTextoSql = (texto) => {
467
+ if (!/\b(?:select\b[\s\S]*?\bfrom\b|insert\s+into|update\s+[A-Za-z_][\w$.-]*\s+set|delete\s+from|create\s+(?:table|view)|alter\s+table|drop\s+(?:table|view)|join\s+[A-Za-z_][\w$.-]*|create\s+(?:unique\s+)?index)\b/i.test(texto)) {
468
+ return;
469
+ }
470
+ for (const match of texto.matchAll(/\bcreate\s+(?:or\s+replace\s+)?(table|view)\s+(?:if\s+not\s+exists\s+)?["'`]?([A-Za-z_][\w$.-]*)["'`]?/gi)) {
471
+ registrarParaMotores(match[1].toLowerCase(), match[2]);
472
+ }
473
+ for (const match of texto.matchAll(/\bcreate\s+(?:unique\s+)?index\s+(?:if\s+not\s+exists\s+)?["'`]?([A-Za-z_][\w$.-]*)["'`]?/gi)) {
474
+ registrarParaMotores("index", match[1]);
475
+ }
476
+ for (const match of texto.matchAll(/\b(?:insert\s+into|update|from|join|delete\s+from|truncate\s+table)\s+["'`]?([A-Za-z_][\w$.-]*)["'`]?/gi)) {
477
+ registrarParaMotores("table", match[1]);
478
+ }
479
+ };
480
+ if (/\.(?:sql|psql|ddl)$/i.test(arquivo)) {
481
+ registrarTextoSql(codigo);
482
+ }
483
+ else {
484
+ for (const literal of codigo.matchAll(/(["'`])([\s\S]*?)\1/g)) {
485
+ registrarTextoSql(literal[2] ?? "");
486
+ }
487
+ }
488
+ for (const match of codigo.matchAll(/\.(?:from|into|table)\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/gi)) {
489
+ registrarParaMotores("table", match[1]);
490
+ }
491
+ for (const match of codigo.matchAll(/\b(?:knex|db|trx)\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/gi)) {
492
+ registrarParaMotores("table", match[1]);
493
+ }
494
+ for (const match of codigo.matchAll(/\bprisma\.([A-Za-z_]\w*)\.(?:find\w+|create|update|delete|upsert|aggregate|count)\b/gi)) {
495
+ registrarParaMotores("table", match[1]);
496
+ }
497
+ return [...recursos.values()];
498
+ }
499
+ function extrairRecursosMongoDb(arquivo, codigo) {
500
+ const recursos = new Map();
501
+ const contextoMongo = /\b(?:mongodb|mongoose|mongoclient|objectid)\b/i.test(codigo)
502
+ || /\bdb\.collection\s*\(/i.test(codigo)
503
+ || /(?:^|\/)mongo(?:db)?(?:\/|[-_.])/i.test(normalizarFragmentoArquivo(arquivo));
504
+ if (!contextoMongo) {
505
+ return [];
506
+ }
507
+ for (const match of codigo.matchAll(/\b(?:db\.)?collection\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/gi)) {
508
+ registrarRecursoDrift(recursos, "mongodb", "collection", match[1], arquivo);
509
+ }
510
+ for (const match of codigo.matchAll(/\bgetCollection\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/gi)) {
511
+ registrarRecursoDrift(recursos, "mongodb", "collection", match[1], arquivo);
512
+ }
513
+ for (const match of codigo.matchAll(/\bmongoose\.model\s*\(\s*["'`]([^"'`]+)["'`](?:\s*,[\s\S]*?,\s*["'`]([^"'`]+)["'`])?/gi)) {
514
+ registrarRecursoDrift(recursos, "mongodb", "document", match[1], arquivo);
515
+ if (match[2]) {
516
+ registrarRecursoDrift(recursos, "mongodb", "collection", match[2], arquivo);
517
+ }
518
+ }
519
+ for (const match of codigo.matchAll(/\bdb\.([A-Za-z_]\w*)\.(?:find|findOne|aggregate|insertOne|insertMany|updateOne|updateMany|deleteOne|deleteMany|countDocuments)\b/gi)) {
520
+ registrarRecursoDrift(recursos, "mongodb", "collection", match[1], arquivo);
521
+ }
522
+ return [...recursos.values()];
523
+ }
524
+ function extrairRecursosRedis(arquivo, codigo) {
525
+ const recursos = new Map();
526
+ const contextoRedis = /\b(?:from|require)\s*\(?["'`](?:redis|ioredis)["'`]/i.test(codigo)
527
+ || /\bcreateClient\s*\(/i.test(codigo)
528
+ || /\bx(?:add|read|readgroup|groupcreate|groupdestroy)\s*\(/i.test(codigo)
529
+ || /(?:^|\/)redis(?:\/|[-_.])/i.test(normalizarFragmentoArquivo(arquivo));
530
+ if (!contextoRedis) {
531
+ return [];
532
+ }
533
+ const operacoesKeyspace = OPERACOES_REDIS_KEYSPACE.join("|");
534
+ const operacoesStream = OPERACOES_REDIS_STREAM.join("|");
535
+ const padraoKeyspace = new RegExp(`\\b(?:${operacoesKeyspace})\\s*\\(\\s*['"\\\`]([^'"\\\`]+)['"\\\`]`, "gi");
536
+ const padraoStream = new RegExp(`\\b(?:${operacoesStream})\\s*\\(\\s*['"\\\`]([^'"\\\`]+)['"\\\`]`, "gi");
537
+ for (const match of codigo.matchAll(padraoKeyspace)) {
538
+ registrarRecursoDrift(recursos, "redis", "keyspace", match[1], arquivo);
539
+ }
540
+ for (const match of codigo.matchAll(padraoStream)) {
541
+ registrarRecursoDrift(recursos, "redis", "stream", match[1], arquivo);
542
+ }
543
+ return [...recursos.values()];
544
+ }
545
+ function extrairRecursosPersistenciaCodigoVivo(arquivo, codigo) {
546
+ const recursos = new Map();
547
+ for (const recurso of extrairColecoesFirebase(arquivo, codigo)) {
548
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
549
+ }
550
+ for (const recurso of extrairRecursosSql(arquivo, codigo)) {
551
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
552
+ }
553
+ for (const recurso of extrairRecursosMongoDb(arquivo, codigo)) {
554
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
555
+ }
556
+ for (const recurso of extrairRecursosRedis(arquivo, codigo)) {
557
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
558
+ }
559
+ return [...recursos.values()];
560
+ }
561
+ function extrairRecursosPrisma(arquivo, codigo) {
562
+ const recursos = new Map();
563
+ const provider = codigo.match(/\bprovider\s*=\s*["'`](postgresql|mysql|sqlite)["'`]/i)?.[1]?.toLowerCase();
564
+ const origem = provider === "postgresql"
565
+ ? "postgres"
566
+ : provider === "mysql"
567
+ ? "mysql"
568
+ : provider === "sqlite"
569
+ ? "sqlite"
570
+ : undefined;
571
+ if (!origem) {
572
+ return [];
573
+ }
574
+ for (const match of codigo.matchAll(/\bmodel\s+([A-Za-z_]\w*)\s*\{([\s\S]*?)\n\}/g)) {
575
+ const nomeModelo = match[1];
576
+ const corpo = match[2] ?? "";
577
+ const tabelaMapeada = corpo.match(/@@map\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/)?.[1];
578
+ registrarRecursoDrift(recursos, origem, "table", tabelaMapeada ?? nomeModelo, arquivo);
579
+ if (tabelaMapeada) {
580
+ registrarRecursoDrift(recursos, origem, "table", nomeModelo, arquivo);
581
+ }
582
+ }
583
+ return [...recursos.values()];
584
+ }
31
585
  function escolherArquivoPorVinculo(arquivos, valor) {
32
586
  const normalizado = normalizarFragmentoArquivo(valor);
33
587
  const exato = arquivos.find((arquivo) => normalizarFragmentoArquivo(arquivo) === normalizado);
@@ -210,14 +764,35 @@ function resumirOperacional(resultado) {
210
764
  ? Math.round(resultado.tasks.reduce((total, task) => total + task.scoreSemantico, 0) / resultado.tasks.length)
211
765
  : 0;
212
766
  const confiancaGeral = scoreMedio >= 80 ? "alta" : scoreMedio >= 55 ? "media" : "baixa";
213
- const riscosPrincipais = [...new Set(resultado.tasks.filter((task) => task.riscoOperacional !== "baixo").map((task) => `${task.task}:${task.riscoOperacional}`))];
214
- const oQueTocar = [...new Set(resultado.tasks.flatMap((task) => task.arquivosProvaveisEditar))].slice(0, 20);
215
- const oQueValidar = [...new Set(resultado.tasks.flatMap((task) => task.checksSugeridos))];
216
- const oQueEstaFrouxo = [...new Set(resultado.tasks.flatMap((task) => task.lacunas))];
767
+ const riscosPrincipais = [...new Set([
768
+ ...resultado.tasks.filter((task) => task.riscoOperacional !== "baixo").map((task) => `${task.task}:${task.riscoOperacional}`),
769
+ ...resultado.persistencia_real
770
+ .filter((item) => item.status !== "materializado")
771
+ .map((item) => `${item.task}:${item.alvo}:persistencia_${item.status}`),
772
+ ])];
773
+ const oQueTocar = [...new Set([
774
+ ...resultado.tasks.flatMap((task) => task.arquivosProvaveisEditar),
775
+ ...resultado.persistencia_real.flatMap((item) => [...item.arquivos, ...item.repositorios]),
776
+ ])].slice(0, 20);
777
+ const oQueValidar = [...new Set([
778
+ ...resultado.tasks.flatMap((task) => task.checksSugeridos),
779
+ ...resultado.persistencia_real
780
+ .filter((item) => item.status !== "materializado")
781
+ .map((item) => `validar persistencia real de ${item.task} em ${item.alvo}`),
782
+ ])];
783
+ const oQueEstaFrouxo = [...new Set([
784
+ ...resultado.tasks.flatMap((task) => task.lacunas),
785
+ ...resultado.persistencia_real
786
+ .filter((item) => item.status !== "materializado" || item.compatibilidade === "desconhecida" || item.compatibilidade === "invalido")
787
+ .map((item) => `persistencia:${item.alvo}:${item.status}:${item.compatibilidade}`),
788
+ ])];
217
789
  const oQueFoiInferido = [
218
790
  ...new Set([
219
791
  ...resultado.impls_quebrados.flatMap((impl) => impl.candidatos?.map((candidato) => candidato.caminho) ?? []),
220
792
  ...resultado.vinculos_quebrados.filter((vinculo) => vinculo.status === "parcial").map((vinculo) => `${vinculo.dono}:${vinculo.valor}`),
793
+ ...resultado.persistencia_real
794
+ .filter((item) => item.compatibilidade === "desconhecida")
795
+ .map((item) => `${item.task}:${item.alvo}:compatibilidade_nao_confirmada`),
221
796
  ]),
222
797
  ];
223
798
  return {
@@ -286,7 +861,7 @@ async function listarArquivosRecursivos(diretorio, extensoes) {
286
861
  }
287
862
  const encontrados = [];
288
863
  for (const entrada of entradas) {
289
- if (DIRETORIOS_IGNORADOS.has(entrada.name)) {
864
+ if (diretoriosIgnoradosAtivos.has(entrada.name.toLowerCase())) {
290
865
  continue;
291
866
  }
292
867
  const caminhoAtual = path.join(diretorio, entrada.name);
@@ -701,8 +1276,8 @@ async function indexarTypeScript(diretorios) {
701
1276
  const sourceFile = ts.createSourceFile(arquivo, codigo, ts.ScriptTarget.Latest, true, scriptKind);
702
1277
  const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
703
1278
  const relacao = path.relative(diretorio, arquivo);
704
- for (const recurso of extrairColecoesFirebase(arquivo, codigo)) {
705
- recursos.set(`${recurso.nome}:${recurso.arquivo}:${recurso.tipo}`, recurso);
1279
+ for (const recurso of extrairRecursosPersistenciaCodigoVivo(arquivo, codigo)) {
1280
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
706
1281
  }
707
1282
  for (const rota of extrairRotasTypeScriptHttp(sourceFile, relacao)) {
708
1283
  rotas.push({
@@ -889,6 +1464,7 @@ function registrarRotasPython(rotas, decoratorsPendentes, prefixo, arquivo, nome
889
1464
  async function indexarPython(diretorios) {
890
1465
  const simbolos = new Map();
891
1466
  const rotas = [];
1467
+ const recursos = new Map();
892
1468
  for (const diretorio of diretorios) {
893
1469
  const arquivos = (await listarArquivosRecursivos(diretorio, [".py"]))
894
1470
  .filter((arquivo) => !arquivo.endsWith("__init__.py") && !/tests?[\\/]/i.test(arquivo));
@@ -896,6 +1472,9 @@ async function indexarPython(diretorios) {
896
1472
  const texto = await readFile(arquivo, "utf8");
897
1473
  const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
898
1474
  const prefixo = texto.match(/APIRouter\s*\(\s*prefix\s*=\s*["']([^"']+)["']/)?.[1];
1475
+ for (const recurso of extrairRecursosPersistenciaCodigoVivo(arquivo, texto)) {
1476
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
1477
+ }
899
1478
  for (const rota of extrairRotasFlaskDecoradas(texto)) {
900
1479
  rotas.push({
901
1480
  origem: "flask",
@@ -947,11 +1526,12 @@ async function indexarPython(diretorios) {
947
1526
  }
948
1527
  }
949
1528
  }
950
- return { simbolos: [...simbolos.values()], rotas };
1529
+ return { simbolos: [...simbolos.values()], rotas, recursos: [...recursos.values()] };
951
1530
  }
952
1531
  async function indexarDart(diretorios) {
953
1532
  const simbolos = new Map();
954
1533
  const rotas = [];
1534
+ const recursos = new Map();
955
1535
  const consumerSurfaces = new Map();
956
1536
  for (const diretorio of diretorios) {
957
1537
  const arquivos = (await listarArquivosRecursivos(diretorio, [".dart"]))
@@ -960,6 +1540,9 @@ async function indexarDart(diretorios) {
960
1540
  const texto = await readFile(arquivo, "utf8");
961
1541
  const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
962
1542
  const relacao = path.relative(diretorio, arquivo);
1543
+ for (const recurso of extrairRecursosPersistenciaCodigoVivo(arquivo, texto)) {
1544
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
1545
+ }
963
1546
  for (const match of texto.matchAll(/(?:Future<[^\n]+>|[\w?<>.,\s]+)\s+(\w+)\(([^)]*)\)\s*(?:async\s*)?\{/g)) {
964
1547
  const nome = match[1];
965
1548
  if (["build", "toString"].includes(nome)) {
@@ -1006,6 +1589,7 @@ async function indexarDart(diretorios) {
1006
1589
  return {
1007
1590
  simbolos: [...simbolos.values()],
1008
1591
  rotas,
1592
+ recursos: [...recursos.values()],
1009
1593
  consumerSurfaces: [...consumerSurfaces.values()].sort((a, b) => a.rota.localeCompare(b.rota, "pt-BR")
1010
1594
  || a.tipoArquivo.localeCompare(b.tipoArquivo, "pt-BR")
1011
1595
  || a.arquivo.localeCompare(b.arquivo, "pt-BR")),
@@ -1037,12 +1621,16 @@ function registrarSimboloGenerico(simbolos, origem, basesSimbolicas, arquivo, si
1037
1621
  async function indexarDotnet(diretorios) {
1038
1622
  const simbolos = new Map();
1039
1623
  const rotas = [];
1624
+ const recursos = new Map();
1040
1625
  for (const diretorio of diretorios) {
1041
1626
  const arquivos = (await listarArquivosRecursivos(diretorio, [".cs"]))
1042
1627
  .filter((arquivo) => !/(^|[\\/])(bin|obj|Test[s]?)([\\/]|$)/i.test(arquivo));
1043
1628
  for (const arquivo of arquivos) {
1044
1629
  const codigo = await readFile(arquivo, "utf8");
1045
1630
  const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
1631
+ for (const recurso of extrairRecursosPersistenciaCodigoVivo(arquivo, codigo)) {
1632
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
1633
+ }
1046
1634
  for (const simbolo of extrairSimbolosDotnet(codigo)) {
1047
1635
  registrarSimboloGenerico(simbolos, "cs", basesSimbolicas, arquivo, simbolo.simbolo);
1048
1636
  }
@@ -1057,17 +1645,21 @@ async function indexarDotnet(diretorios) {
1057
1645
  }
1058
1646
  }
1059
1647
  }
1060
- return { simbolos: [...simbolos.values()], rotas };
1648
+ return { simbolos: [...simbolos.values()], rotas, recursos: [...recursos.values()] };
1061
1649
  }
1062
1650
  async function indexarJava(diretorios) {
1063
1651
  const simbolos = new Map();
1064
1652
  const rotas = [];
1653
+ const recursos = new Map();
1065
1654
  for (const diretorio of diretorios) {
1066
1655
  const arquivos = (await listarArquivosRecursivos(diretorio, [".java"]))
1067
1656
  .filter((arquivo) => !/(^|[\\/])(target|build|out|Test[s]?)([\\/]|$)/i.test(arquivo));
1068
1657
  for (const arquivo of arquivos) {
1069
1658
  const codigo = await readFile(arquivo, "utf8");
1070
1659
  const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
1660
+ for (const recurso of extrairRecursosPersistenciaCodigoVivo(arquivo, codigo)) {
1661
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
1662
+ }
1071
1663
  for (const simbolo of extrairSimbolosJava(codigo)) {
1072
1664
  registrarSimboloGenerico(simbolos, "java", basesSimbolicas, arquivo, simbolo.simbolo);
1073
1665
  }
@@ -1082,16 +1674,20 @@ async function indexarJava(diretorios) {
1082
1674
  }
1083
1675
  }
1084
1676
  }
1085
- return { simbolos: [...simbolos.values()], rotas };
1677
+ return { simbolos: [...simbolos.values()], rotas, recursos: [...recursos.values()] };
1086
1678
  }
1087
1679
  async function indexarGo(diretorios) {
1088
1680
  const simbolos = new Map();
1089
1681
  const rotas = [];
1682
+ const recursos = new Map();
1090
1683
  for (const diretorio of diretorios) {
1091
1684
  const arquivos = await listarArquivosRecursivos(diretorio, [".go"]);
1092
1685
  for (const arquivo of arquivos) {
1093
1686
  const codigo = await readFile(arquivo, "utf8");
1094
1687
  const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
1688
+ for (const recurso of extrairRecursosPersistenciaCodigoVivo(arquivo, codigo)) {
1689
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
1690
+ }
1095
1691
  for (const simbolo of extrairSimbolosGo(codigo)) {
1096
1692
  registrarSimboloGenerico(simbolos, "go", basesSimbolicas, arquivo, simbolo.simbolo);
1097
1693
  }
@@ -1106,16 +1702,20 @@ async function indexarGo(diretorios) {
1106
1702
  }
1107
1703
  }
1108
1704
  }
1109
- return { simbolos: [...simbolos.values()], rotas };
1705
+ return { simbolos: [...simbolos.values()], rotas, recursos: [...recursos.values()] };
1110
1706
  }
1111
1707
  async function indexarRust(diretorios) {
1112
1708
  const simbolos = new Map();
1113
1709
  const rotas = [];
1710
+ const recursos = new Map();
1114
1711
  for (const diretorio of diretorios) {
1115
1712
  const arquivos = await listarArquivosRecursivos(diretorio, [".rs"]);
1116
1713
  for (const arquivo of arquivos) {
1117
1714
  const codigo = await readFile(arquivo, "utf8");
1118
1715
  const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
1716
+ for (const recurso of extrairRecursosPersistenciaCodigoVivo(arquivo, codigo)) {
1717
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
1718
+ }
1119
1719
  for (const simbolo of extrairSimbolosRust(codigo)) {
1120
1720
  registrarSimboloGenerico(simbolos, "rust", basesSimbolicas, arquivo, simbolo.simbolo);
1121
1721
  }
@@ -1130,22 +1730,312 @@ async function indexarRust(diretorios) {
1130
1730
  }
1131
1731
  }
1132
1732
  }
1133
- return { simbolos: [...simbolos.values()], rotas };
1733
+ return { simbolos: [...simbolos.values()], rotas, recursos: [...recursos.values()] };
1134
1734
  }
1135
1735
  async function indexarCpp(diretorios) {
1136
1736
  const simbolos = new Map();
1737
+ const recursos = new Map();
1137
1738
  for (const diretorio of diretorios) {
1138
1739
  const arquivos = (await listarArquivosRecursivos(diretorio, [".cpp", ".cc", ".cxx", ".hpp", ".h"]))
1139
1740
  .filter((arquivo) => !/(^|[\\/])(windows|linux|macos|runner|flutter|ephemeral|build|vendor)([\\/]|$)/i.test(arquivo));
1140
1741
  for (const arquivo of arquivos) {
1141
1742
  const codigo = await readFile(arquivo, "utf8");
1142
1743
  const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
1744
+ for (const recurso of extrairRecursosPersistenciaCodigoVivo(arquivo, codigo)) {
1745
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
1746
+ }
1143
1747
  for (const simbolo of extrairSimbolosCpp(codigo)) {
1144
1748
  registrarSimboloGenerico(simbolos, "cpp", basesSimbolicas, arquivo, simbolo.simbolo);
1145
1749
  }
1146
1750
  }
1147
1751
  }
1148
- return [...simbolos.values()];
1752
+ return {
1753
+ simbolos: [...simbolos.values()],
1754
+ recursos: [...recursos.values()],
1755
+ };
1756
+ }
1757
+ async function indexarPersistenciaDeclarativa(diretorios) {
1758
+ const recursos = new Map();
1759
+ for (const diretorio of diretorios) {
1760
+ const arquivos = await listarArquivosRecursivos(diretorio, [".sql", ".psql", ".ddl", ".prisma"]);
1761
+ for (const arquivo of arquivos) {
1762
+ const codigo = await readFile(arquivo, "utf8");
1763
+ const extracoes = arquivo.endsWith(".prisma")
1764
+ ? extrairRecursosPrisma(arquivo, codigo)
1765
+ : extrairRecursosPersistenciaCodigoVivo(arquivo, codigo);
1766
+ for (const recurso of extracoes) {
1767
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
1768
+ }
1769
+ }
1770
+ }
1771
+ return { recursos: [...recursos.values()] };
1772
+ }
1773
+ function normalizarOrigemParaEngine(origem) {
1774
+ return origem && origem !== "firebase" ? origem : undefined;
1775
+ }
1776
+ function registrarColunaPersistenciaDrift(colunas, engine, recurso, coluna, arquivo) {
1777
+ const recursoNormalizado = fecharPrefixoRecurso(limparLiteralRecurso(recurso));
1778
+ const colunaNormalizada = fecharPrefixoRecurso(limparLiteralRecurso(coluna));
1779
+ if (!recursoNormalizado || !colunaNormalizada || recursoEhIgnorado(colunaNormalizada)) {
1780
+ return;
1781
+ }
1782
+ const chave = `${engine}:${normalizarNomeRecursoDrift(recursoNormalizado)}:${normalizarNomeRecursoDrift(colunaNormalizada)}:${arquivo}`;
1783
+ if (!colunas.has(chave)) {
1784
+ colunas.set(chave, {
1785
+ engine,
1786
+ recurso: recursoNormalizado,
1787
+ coluna: colunaNormalizada,
1788
+ arquivo,
1789
+ });
1790
+ }
1791
+ }
1792
+ function registrarRepositorioPersistenciaDrift(repositorios, engine, recurso, arquivo) {
1793
+ const recursoNormalizado = fecharPrefixoRecurso(limparLiteralRecurso(recurso));
1794
+ if (!recursoNormalizado) {
1795
+ return;
1796
+ }
1797
+ const chave = `${engine}:${normalizarNomeRecursoDrift(recursoNormalizado)}:${arquivo}`;
1798
+ if (!repositorios.has(chave)) {
1799
+ repositorios.set(chave, {
1800
+ engine,
1801
+ recurso: recursoNormalizado,
1802
+ arquivo,
1803
+ });
1804
+ }
1805
+ }
1806
+ function extrairColunasSqlDetalhadas(arquivo, codigo) {
1807
+ const colunas = new Map();
1808
+ const motores = inferirMotoresRelacionais(codigo, arquivo);
1809
+ if (motores.length === 0) {
1810
+ return [];
1811
+ }
1812
+ const registrarParaMotores = (recurso, coluna) => {
1813
+ for (const motor of motores) {
1814
+ registrarColunaPersistenciaDrift(colunas, motor, recurso, coluna, arquivo);
1815
+ }
1816
+ };
1817
+ for (const match of codigo.matchAll(/\bcreate\s+table\s+(?:if\s+not\s+exists\s+)?["'`]?([A-Za-z_][\w$.-]*)["'`]?\s*\(([\s\S]*?)\)\s*;?/gi)) {
1818
+ const tabela = match[1];
1819
+ const corpo = match[2] ?? "";
1820
+ for (const linha of corpo.split(/\r?\n|,/)) {
1821
+ const trecho = linha.trim();
1822
+ if (!trecho || /^(?:constraint|primary|foreign|unique|check|key|index)\b/i.test(trecho)) {
1823
+ continue;
1824
+ }
1825
+ const coluna = trecho.match(/^["'`]?([A-Za-z_][\w$.-]*)["'`]?/i)?.[1];
1826
+ if (coluna) {
1827
+ registrarParaMotores(tabela, coluna);
1828
+ }
1829
+ }
1830
+ }
1831
+ for (const match of codigo.matchAll(/\binsert\s+into\s+["'`]?([A-Za-z_][\w$.-]*)["'`]?\s*\(([^)]+)\)/gi)) {
1832
+ for (const coluna of (match[2] ?? "").split(",").map((item) => item.trim())) {
1833
+ registrarParaMotores(match[1], coluna);
1834
+ }
1835
+ }
1836
+ for (const match of codigo.matchAll(/\bupdate\s+["'`]?([A-Za-z_][\w$.-]*)["'`]?\s+set\s+([\s\S]*?)(?:\bwhere\b|;|$)/gi)) {
1837
+ for (const coluna of (match[2] ?? "").split(",").map((item) => item.split("=")[0]?.trim() ?? "")) {
1838
+ registrarParaMotores(match[1], coluna);
1839
+ }
1840
+ }
1841
+ for (const match of codigo.matchAll(/\bselect\s+([\s\S]*?)\s+from\s+["'`]?([A-Za-z_][\w$.-]*)["'`]?/gi)) {
1842
+ const tabela = match[2];
1843
+ const lista = (match[1] ?? "").trim();
1844
+ if (!lista || lista === "*") {
1845
+ continue;
1846
+ }
1847
+ for (const coluna of lista.split(",").map((item) => item.trim().split(/\s+as\s+/i)[0] ?? "")) {
1848
+ const nome = coluna.split(".").at(-1) ?? coluna;
1849
+ registrarParaMotores(tabela, nome);
1850
+ }
1851
+ }
1852
+ return [...colunas.values()];
1853
+ }
1854
+ function extrairColunasPrismaDetalhadas(arquivo, codigo) {
1855
+ const colunas = new Map();
1856
+ const provider = codigo.match(/\bprovider\s*=\s*["'`](postgresql|mysql|sqlite)["'`]/i)?.[1]?.toLowerCase();
1857
+ const engine = provider === "postgresql"
1858
+ ? "postgres"
1859
+ : provider === "mysql"
1860
+ ? "mysql"
1861
+ : provider === "sqlite"
1862
+ ? "sqlite"
1863
+ : undefined;
1864
+ if (!engine) {
1865
+ return [];
1866
+ }
1867
+ for (const match of codigo.matchAll(/\bmodel\s+([A-Za-z_]\w*)\s*\{([\s\S]*?)\n\}/g)) {
1868
+ const nomeModelo = match[1];
1869
+ const corpo = match[2] ?? "";
1870
+ const tabela = corpo.match(/@@map\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/)?.[1] ?? nomeModelo;
1871
+ for (const linha of corpo.split(/\r?\n/)) {
1872
+ const limpa = linha.trim();
1873
+ if (!limpa || limpa.startsWith("@@") || limpa.startsWith("//")) {
1874
+ continue;
1875
+ }
1876
+ const coluna = limpa.match(/^([A-Za-z_]\w*)\s+/)?.[1];
1877
+ const colunaMapeada = limpa.match(/@map\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/)?.[1];
1878
+ if (coluna) {
1879
+ registrarColunaPersistenciaDrift(colunas, engine, tabela, colunaMapeada ?? coluna, arquivo);
1880
+ }
1881
+ }
1882
+ }
1883
+ return [...colunas.values()];
1884
+ }
1885
+ function extrairCamposMongoDetalhados(arquivo, codigo) {
1886
+ const colunas = new Map();
1887
+ const colecoes = extrairRecursosMongoDb(arquivo, codigo).filter((item) => item.tipo === "collection");
1888
+ if (colecoes.length === 0) {
1889
+ return [];
1890
+ }
1891
+ const registrarCampoMongo = (colecao, trecho) => {
1892
+ for (const match of trecho.matchAll(/([A-Za-z_][\w$]*)\s*:/g)) {
1893
+ registrarColunaPersistenciaDrift(colunas, "mongodb", colecao, match[1], arquivo);
1894
+ }
1895
+ };
1896
+ for (const schema of codigo.matchAll(/\bnew\s+Schema\s*\(\s*\{([\s\S]*?)\}\s*\)/g)) {
1897
+ for (const colecao of colecoes) {
1898
+ registrarCampoMongo(colecao.nome, schema[1] ?? "");
1899
+ }
1900
+ }
1901
+ for (const trecho of codigo.matchAll(/\b(?:find(?:One)?|update(?:One|Many)?|insertOne|insertMany)\s*\(\s*\{([\s\S]*?)\}\s*(?:,|\))/g)) {
1902
+ for (const colecao of colecoes) {
1903
+ registrarCampoMongo(colecao.nome, trecho[1] ?? "");
1904
+ }
1905
+ }
1906
+ return [...colunas.values()];
1907
+ }
1908
+ function extrairCamposRedisDetalhados(arquivo, codigo) {
1909
+ const colunas = new Map();
1910
+ for (const match of codigo.matchAll(/\bh(?:set|get|del|exists)\s*\(\s*["'`]([^"'`]+)["'`]\s*,\s*["'`]([^"'`]+)["'`]/gi)) {
1911
+ registrarColunaPersistenciaDrift(colunas, "redis", match[1], match[2], arquivo);
1912
+ }
1913
+ return [...colunas.values()];
1914
+ }
1915
+ function registrarRepositoriosPorRecursos(repositorios, arquivo, codigo, recursos) {
1916
+ const contextoRepositorio = /(?:repository|repositories|repositorio|repositorios|repo|dao|store|queries|persistence|persistencia)/i.test(arquivo)
1917
+ || /\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);
1919
+ if (!contextoRepositorio && !contextoAcesso) {
1920
+ return;
1921
+ }
1922
+ for (const recurso of recursos) {
1923
+ const engine = normalizarOrigemParaEngine(recurso.origem);
1924
+ if (!engine) {
1925
+ continue;
1926
+ }
1927
+ registrarRepositorioPersistenciaDrift(repositorios, engine, recurso.nome, arquivo);
1928
+ }
1929
+ }
1930
+ async function indexarPersistenciaDetalhada(diretorios) {
1931
+ const colunas = new Map();
1932
+ const repositorios = new Map();
1933
+ for (const diretorio of diretorios) {
1934
+ const arquivos = await listarArquivosRecursivos(diretorio, [
1935
+ ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
1936
+ ".py", ".dart", ".cs", ".java", ".go", ".rs", ".cpp", ".cc", ".cxx", ".hpp", ".h",
1937
+ ".sql", ".psql", ".ddl", ".prisma",
1938
+ ]);
1939
+ for (const arquivo of arquivos) {
1940
+ const codigo = await readFile(arquivo, "utf8");
1941
+ const recursos = arquivo.endsWith(".prisma")
1942
+ ? extrairRecursosPrisma(arquivo, codigo)
1943
+ : extrairRecursosPersistenciaCodigoVivo(arquivo, codigo);
1944
+ for (const coluna of extrairColunasSqlDetalhadas(arquivo, codigo)) {
1945
+ registrarColunaPersistenciaDrift(colunas, coluna.engine, coluna.recurso, coluna.coluna, coluna.arquivo);
1946
+ }
1947
+ for (const coluna of extrairColunasPrismaDetalhadas(arquivo, codigo)) {
1948
+ registrarColunaPersistenciaDrift(colunas, coluna.engine, coluna.recurso, coluna.coluna, coluna.arquivo);
1949
+ }
1950
+ for (const coluna of extrairCamposMongoDetalhados(arquivo, codigo)) {
1951
+ registrarColunaPersistenciaDrift(colunas, coluna.engine, coluna.recurso, coluna.coluna, coluna.arquivo);
1952
+ }
1953
+ for (const coluna of extrairCamposRedisDetalhados(arquivo, codigo)) {
1954
+ registrarColunaPersistenciaDrift(colunas, coluna.engine, coluna.recurso, coluna.coluna, coluna.arquivo);
1955
+ }
1956
+ registrarRepositoriosPorRecursos(repositorios, arquivo, codigo, recursos);
1957
+ }
1958
+ }
1959
+ return {
1960
+ colunas: [...colunas.values()],
1961
+ repositorios: [...repositorios.values()],
1962
+ };
1963
+ }
1964
+ function recursoDetalhadoCombina(recurso, esperado) {
1965
+ return variantesNomeRecursoDrift(recurso).some((variante) => esperado.nomes.some((nome) => variantesNomeRecursoDrift(nome).includes(variante)));
1966
+ }
1967
+ function localizarCompatibilidadePersistencia(bancos, esperado, recursoReal) {
1968
+ for (const banco of bancos) {
1969
+ for (const recurso of banco.resources) {
1970
+ if (!recursoPersistenciaCombinaAlvo(recurso, esperado.alvo)) {
1971
+ continue;
1972
+ }
1973
+ const engine = banco.engine ?? normalizarOrigemParaEngine(recursoReal?.origem);
1974
+ const compatibilidade = engine
1975
+ ? recurso.compatibilidade.find((item) => item.engine === engine) ?? recurso.compatibilidade[0]
1976
+ : recurso.compatibilidade[0];
1977
+ return {
1978
+ engine: (engine ?? recursoReal?.origem ?? "desconhecido"),
1979
+ compatibilidade: compatibilidade?.status ?? "desconhecida",
1980
+ motivoCompatibilidade: compatibilidade?.motivo,
1981
+ tipo: recurso.resourceKind ?? recursoReal?.tipo ?? esperado.tiposAceitos[0] ?? "query",
1982
+ };
1983
+ }
1984
+ }
1985
+ return {
1986
+ engine: recursoReal?.origem ?? esperado.origem ?? "desconhecido",
1987
+ compatibilidade: "desconhecida",
1988
+ tipo: recursoReal?.tipo ?? esperado.tiposAceitos[0] ?? "query",
1989
+ };
1990
+ }
1991
+ export async function analisarPersistenciaReal(contexto, mapaRecursos, detalhesPersistencia, opcoes) {
1992
+ const opcoesResolvidas = resolverOpcoesDrift(opcoes);
1993
+ const diretoriosCodigoAtivos = resolverDiretoriosCodigoEscopoReal(contexto, opcoesResolvidas);
1994
+ const mapa = mapaRecursos ?? construirMapaRecursos((await indexarPersistenciaDeclarativa(diretoriosCodigoAtivos)).recursos);
1995
+ const detalhes = detalhesPersistencia ?? await indexarPersistenciaDetalhada(diretoriosCodigoAtivos);
1996
+ const registros = [];
1997
+ for (const item of contexto.modulosSelecionados) {
1998
+ const ir = item.resultado.ir;
1999
+ if (!ir) {
2000
+ continue;
2001
+ }
2002
+ for (const task of ir.tasks) {
2003
+ for (const esperado of extrairRecursosEsperados(task, ir)) {
2004
+ const correspondencias = esperado.nomes.flatMap((nome) => variantesNomeRecursoDrift(nome).flatMap((variante) => (mapa.get(variante) ?? []).filter((recurso) => recursoResolvidoCombinaEsperado(recurso, esperado))));
2005
+ const recursosReais = [...new Map(correspondencias.map((recurso) => [`${recurso.origem}:${recurso.tipo}:${recurso.nome}:${recurso.arquivo}`, recurso])).values()];
2006
+ const compatibilidade = localizarCompatibilidadePersistencia(ir.databases, esperado, recursosReais[0]);
2007
+ const colunas = [...new Set(detalhes.colunas
2008
+ .filter((coluna) => (!normalizarOrigemParaEngine(recursosReais[0]?.origem) || coluna.engine === normalizarOrigemParaEngine(recursosReais[0]?.origem))
2009
+ && recursoDetalhadoCombina(coluna.recurso, esperado))
2010
+ .map((coluna) => coluna.coluna))].sort((a, b) => a.localeCompare(b, "pt-BR"));
2011
+ const repositorios = [...new Set(detalhes.repositorios
2012
+ .filter((repositorio) => (!normalizarOrigemParaEngine(recursosReais[0]?.origem) || repositorio.engine === normalizarOrigemParaEngine(recursosReais[0]?.origem))
2013
+ && recursoDetalhadoCombina(repositorio.recurso, esperado))
2014
+ .map((repositorio) => repositorio.arquivo))].sort((a, b) => a.localeCompare(b, "pt-BR"));
2015
+ const arquivos = [...new Set(recursosReais.map((recurso) => recurso.arquivo))].sort((a, b) => a.localeCompare(b, "pt-BR"));
2016
+ registros.push({
2017
+ modulo: ir.nome,
2018
+ task: task.nome,
2019
+ alvo: esperado.alvo,
2020
+ engine: compatibilidade.engine,
2021
+ tipo: compatibilidade.tipo,
2022
+ status: recursosReais.length === 0
2023
+ ? "divergente"
2024
+ : colunas.length > 0 || repositorios.length > 0
2025
+ ? "materializado"
2026
+ : "parcial",
2027
+ arquivos,
2028
+ colunas,
2029
+ repositorios,
2030
+ compatibilidade: compatibilidade.compatibilidade,
2031
+ motivoCompatibilidade: compatibilidade.motivoCompatibilidade,
2032
+ });
2033
+ }
2034
+ }
2035
+ }
2036
+ return registros.sort((a, b) => a.modulo.localeCompare(b.modulo, "pt-BR")
2037
+ || a.task.localeCompare(b.task, "pt-BR")
2038
+ || a.alvo.localeCompare(b.alvo, "pt-BR"));
1149
2039
  }
1150
2040
  function normalizarCaminhoRota(caminho) {
1151
2041
  if (!caminho) {
@@ -1337,16 +2227,121 @@ function escolherRotasEsperadas(task, fontesLegado) {
1337
2227
  function taskEhBridgeFirebase(task) {
1338
2228
  return task.implementacoesExternas.some((impl) => impl.origem === "ts" && /sema_contract_bridge|collections?|apps\.worker/i.test(impl.caminho));
1339
2229
  }
1340
- function extrairRecursosEsperados(task) {
1341
- if (!taskEhBridgeFirebase(task)) {
1342
- return [];
2230
+ function tiposAceitosParaRecursoPersistencia(recurso) {
2231
+ switch (recurso.resourceKind) {
2232
+ case "table":
2233
+ case "view":
2234
+ case "query":
2235
+ case "index":
2236
+ case "collection":
2237
+ case "document":
2238
+ case "keyspace":
2239
+ case "stream":
2240
+ return [recurso.resourceKind];
2241
+ default:
2242
+ return [];
2243
+ }
2244
+ }
2245
+ function nomesRecursoPersistencia(recurso) {
2246
+ return [...new Set([
2247
+ recurso.nome,
2248
+ recurso.table,
2249
+ recurso.collection,
2250
+ recurso.entity,
2251
+ recurso.path,
2252
+ recurso.surface,
2253
+ ].filter((item) => Boolean(item)))];
2254
+ }
2255
+ function recursoPersistenciaCombinaAlvo(recurso, alvo) {
2256
+ const alvoVariantes = new Set(variantesNomeRecursoDrift(alvo));
2257
+ if (alvoVariantes.size === 0) {
2258
+ return false;
2259
+ }
2260
+ return nomesRecursoPersistencia(recurso).some((nome) => variantesNomeRecursoDrift(nome).some((variacao) => alvoVariantes.has(variacao)));
2261
+ }
2262
+ function extrairRecursosEsperados(task, ir) {
2263
+ const esperados = new Map();
2264
+ const registrar = (esperado) => {
2265
+ const chave = `${esperado.origem ?? "qualquer"}:${esperado.tiposAceitos.join(",")}:${esperado.nomes.join("|")}:${esperado.alvo}`;
2266
+ if (!esperados.has(chave)) {
2267
+ esperados.set(chave, esperado);
2268
+ }
2269
+ };
2270
+ if (taskEhBridgeFirebase(task)) {
2271
+ for (const efeito of task.efeitosEstruturados.filter((item) => item.categoria === "persistencia" && Boolean(item.alvo))) {
2272
+ registrar({
2273
+ categoria: "persistencia",
2274
+ alvo: efeito.alvo,
2275
+ origem: "firebase",
2276
+ tiposAceitos: ["colecao"],
2277
+ nomes: [efeito.alvo],
2278
+ });
2279
+ }
2280
+ }
2281
+ const efeitosPersistencia = task.efeitosEstruturados.filter((efeito) => ["persistencia", "db.read", "db.write"].includes(efeito.categoria) && Boolean(efeito.alvo));
2282
+ if (efeitosPersistencia.length === 0 || ir.databases.length === 0) {
2283
+ return [...esperados.values()];
2284
+ }
2285
+ for (const efeito of efeitosPersistencia) {
2286
+ for (const database of ir.databases) {
2287
+ for (const recurso of database.resources) {
2288
+ const tiposAceitos = tiposAceitosParaRecursoPersistencia(recurso);
2289
+ if (tiposAceitos.length === 0 || !recursoPersistenciaCombinaAlvo(recurso, efeito.alvo)) {
2290
+ continue;
2291
+ }
2292
+ registrar({
2293
+ categoria: "persistencia",
2294
+ alvo: efeito.alvo,
2295
+ origem: database.engine,
2296
+ tiposAceitos,
2297
+ nomes: nomesRecursoPersistencia(recurso),
2298
+ });
2299
+ }
2300
+ }
2301
+ }
2302
+ return [...esperados.values()];
2303
+ }
2304
+ function construirMapaRecursos(recursos) {
2305
+ const mapa = new Map();
2306
+ for (const recurso of recursos) {
2307
+ for (const variante of variantesNomeRecursoDrift(recurso.nome)) {
2308
+ const existentes = mapa.get(variante) ?? [];
2309
+ if (!existentes.some((item) => item.origem === recurso.origem
2310
+ && item.tipo === recurso.tipo
2311
+ && item.arquivo === recurso.arquivo
2312
+ && item.nome === recurso.nome
2313
+ && item.simbolo === recurso.simbolo)) {
2314
+ existentes.push(recurso);
2315
+ mapa.set(variante, existentes);
2316
+ }
2317
+ }
2318
+ }
2319
+ return mapa;
2320
+ }
2321
+ function recursoResolvidoCombinaEsperado(recurso, esperado) {
2322
+ if (esperado.origem && recurso.origem !== esperado.origem) {
2323
+ return false;
2324
+ }
2325
+ if (esperado.tiposAceitos.length > 0 && !esperado.tiposAceitos.includes(recurso.tipo)) {
2326
+ return false;
2327
+ }
2328
+ const recursoVariantes = new Set(variantesNomeRecursoDrift(recurso.nome));
2329
+ return esperado.nomes.some((nome) => variantesNomeRecursoDrift(nome).some((variante) => recursoVariantes.has(variante)));
2330
+ }
2331
+ function resolverRecursoEsperado(mapaRecursos, esperado, arquivosPreferidos) {
2332
+ const candidatos = new Map();
2333
+ for (const nome of esperado.nomes) {
2334
+ for (const variante of variantesNomeRecursoDrift(nome)) {
2335
+ for (const recurso of mapaRecursos.get(variante) ?? []) {
2336
+ if (recursoResolvidoCombinaEsperado(recurso, esperado)) {
2337
+ candidatos.set(`${recurso.origem}:${recurso.tipo}:${recurso.nome}:${recurso.arquivo}:${recurso.simbolo ?? ""}`, recurso);
2338
+ }
2339
+ }
2340
+ }
1343
2341
  }
1344
- return task.efeitosEstruturados
1345
- .filter((efeito) => efeito.categoria === "persistencia" && Boolean(efeito.alvo))
1346
- .map((efeito) => ({
1347
- categoria: "persistencia",
1348
- alvo: efeito.alvo,
1349
- }));
2342
+ return [...candidatos.values()].sort((a, b) => Number(Boolean(arquivosPreferidos?.has(b.arquivo))) - Number(Boolean(arquivosPreferidos?.has(a.arquivo)))
2343
+ || a.arquivo.localeCompare(b.arquivo, "pt-BR")
2344
+ || a.nome.localeCompare(b.nome, "pt-BR"))[0];
1350
2345
  }
1351
2346
  function coletarVinculosIr(ir) {
1352
2347
  return [
@@ -1357,580 +2352,920 @@ function coletarVinculosIr(ir) {
1357
2352
  ...ir.superficies.flatMap((superficie) => superficie.vinculos.map((vinculo) => ({ donoTipo: "superficie", dono: `${superficie.tipo}:${superficie.nome}`, vinculo }))),
1358
2353
  ];
1359
2354
  }
1360
- export async function analisarDriftLegado(contexto) {
1361
- const indexTs = await indexarTypeScript(contexto.diretoriosCodigo);
1362
- const indexPy = await indexarPython(contexto.diretoriosCodigo);
1363
- const indexDart = await indexarDart(contexto.diretoriosCodigo);
1364
- const indexDotnet = await indexarDotnet(contexto.diretoriosCodigo);
1365
- const indexJava = await indexarJava(contexto.diretoriosCodigo);
1366
- const indexGo = await indexarGo(contexto.diretoriosCodigo);
1367
- const indexRust = await indexarRust(contexto.diretoriosCodigo);
1368
- const indexCpp = await indexarCpp(contexto.diretoriosCodigo);
1369
- const todosSimbolos = [
1370
- ...indexTs.simbolos,
1371
- ...indexPy.simbolos,
1372
- ...indexDart.simbolos,
1373
- ...indexDotnet.simbolos,
1374
- ...indexJava.simbolos,
1375
- ...indexGo.simbolos,
1376
- ...indexRust.simbolos,
1377
- ...indexCpp,
1378
- ];
1379
- const mapaImpl = new Map([
1380
- ...indexTs.simbolos.map((item) => [item.caminho, item]),
1381
- ...indexPy.simbolos.map((item) => [item.caminho, item]),
1382
- ...indexDart.simbolos.map((item) => [item.caminho, item]),
1383
- ...indexDotnet.simbolos.map((item) => [item.caminho, item]),
1384
- ...indexJava.simbolos.map((item) => [item.caminho, item]),
1385
- ...indexGo.simbolos.map((item) => [item.caminho, item]),
1386
- ...indexRust.simbolos.map((item) => [item.caminho, item]),
1387
- ...indexCpp.map((item) => [item.caminho, item]),
1388
- ]);
1389
- const mapaRecursos = new Map(indexTs.recursos.map((item) => [item.nome, item]));
1390
- const todasRotasIndexadas = [
1391
- ...indexTs.rotas,
1392
- ...indexPy.rotas,
1393
- ...indexDart.rotas,
1394
- ...indexDotnet.rotas,
1395
- ...indexJava.rotas,
1396
- ...indexGo.rotas,
1397
- ...indexRust.rotas,
1398
- ];
1399
- const todosArquivosConhecidos = [...new Set([
1400
- ...todosSimbolos.map((item) => item.arquivo),
1401
- ...todasRotasIndexadas.map((item) => item.arquivo),
1402
- ...indexTs.recursos.map((item) => item.arquivo),
1403
- ])].sort((a, b) => a.localeCompare(b, "pt-BR"));
1404
- const implsValidos = [];
1405
- const implsQuebrados = [];
1406
- const vinculosValidos = [];
1407
- const vinculosQuebrados = [];
1408
- const rotasDivergentes = [];
1409
- const recursosValidos = [];
1410
- const recursosDivergentes = [];
1411
- const diagnosticos = [];
1412
- const tasksResumo = [];
1413
- const taskPorChave = new Map();
1414
- const guardrailsPorTask = new Map();
1415
- const resumoVinculosPorTask = new Map();
1416
- for (const item of contexto.modulosSelecionados) {
1417
- const ir = item.resultado.ir;
1418
- if (!ir) {
1419
- continue;
1420
- }
1421
- const superficiesPorChave = new Map(ir.superficies.map((superficie) => [`${superficie.tipo}:${superficie.nome}`, superficie]));
1422
- for (const task of ir.tasks) {
1423
- guardrailsPorTask.set(`${ir.nome}:${task.nome}`, {
1424
- publica: false,
1425
- sensivel: calcularRiscoOperacional(task) === "alto",
1426
- auth: task.auth.explicita,
1427
- authz: task.authz.explicita,
1428
- dados: task.dados.explicita,
1429
- audit: task.audit.explicita,
1430
- segredos: task.segredos.explicita,
1431
- forbidden: task.forbidden.explicita,
1432
- dadosSensiveis: Boolean(task.dados.classificacaoPadrao && ["pii", "financeiro", "credencial", "segredo"].includes(task.dados.classificacaoPadrao)
1433
- || task.dados.campos.some((campo) => ["pii", "financeiro", "credencial", "segredo"].includes(campo.classificacao))),
1434
- efeitoPrivilegiado: task.efeitosEstruturados.some((efeito) => ["db.read", "db.write", "queue.publish", "queue.consume", "fs.read", "fs.write", "network.egress", "secret.read", "shell.exec"].includes(efeito.categoria)
1435
- || ["alta", "critica"].includes(efeito.criticidade ?? "")),
1436
- exigeSegredos: task.efeitosEstruturados.some((efeito) => efeito.categoria === "secret.read")
1437
- || Boolean(task.dados.classificacaoPadrao && ["credencial", "segredo"].includes(task.dados.classificacaoPadrao)
1438
- || task.dados.campos.some((campo) => ["credencial", "segredo"].includes(campo.classificacao))),
1439
- });
1440
- }
1441
- for (const route of ir.routes) {
1442
- if (!route.task || route.perfilCompatibilidade !== "publico") {
2355
+ export async function analisarDriftLegado(contexto, opcoes) {
2356
+ const opcoesResolvidas = resolverOpcoesDrift(opcoes);
2357
+ const configuracaoEscopo = {
2358
+ escopo: opcoesResolvidas.escopo,
2359
+ ignorarWorktrees: opcoesResolvidas.ignorarWorktrees,
2360
+ ignorarConsumidoresLaterais: opcoesResolvidas.ignorarConsumidoresLaterais,
2361
+ termosEscopo: extrairTermosEscopoDrift(contexto, opcoesResolvidas.escopo),
2362
+ };
2363
+ const diretoriosIgnoradosAnteriores = diretoriosIgnoradosAtivos;
2364
+ diretoriosIgnoradosAtivos = resolverDiretoriosIgnoradosAtivos(opcoesResolvidas);
2365
+ try {
2366
+ const diretoriosCodigoAtivos = resolverDiretoriosCodigoEscopoReal(contexto, configuracaoEscopo);
2367
+ const indexTs = await indexarTypeScript(diretoriosCodigoAtivos);
2368
+ const indexPy = await indexarPython(diretoriosCodigoAtivos);
2369
+ const indexDart = await indexarDart(diretoriosCodigoAtivos);
2370
+ const indexDotnet = await indexarDotnet(diretoriosCodigoAtivos);
2371
+ const indexJava = await indexarJava(diretoriosCodigoAtivos);
2372
+ const indexGo = await indexarGo(diretoriosCodigoAtivos);
2373
+ const indexRust = await indexarRust(diretoriosCodigoAtivos);
2374
+ const indexPersistencia = await indexarPersistenciaDeclarativa(diretoriosCodigoAtivos);
2375
+ const detalhesPersistencia = await indexarPersistenciaDetalhada(diretoriosCodigoAtivos);
2376
+ const indexCpp = await indexarCpp(diretoriosCodigoAtivos);
2377
+ const todosSimbolos = [
2378
+ ...indexTs.simbolos,
2379
+ ...indexPy.simbolos,
2380
+ ...indexDart.simbolos,
2381
+ ...indexDotnet.simbolos,
2382
+ ...indexJava.simbolos,
2383
+ ...indexGo.simbolos,
2384
+ ...indexRust.simbolos,
2385
+ ...indexCpp.simbolos,
2386
+ ];
2387
+ const mapaImpl = new Map([
2388
+ ...indexTs.simbolos.map((item) => [item.caminho, item]),
2389
+ ...indexPy.simbolos.map((item) => [item.caminho, item]),
2390
+ ...indexDart.simbolos.map((item) => [item.caminho, item]),
2391
+ ...indexDotnet.simbolos.map((item) => [item.caminho, item]),
2392
+ ...indexJava.simbolos.map((item) => [item.caminho, item]),
2393
+ ...indexGo.simbolos.map((item) => [item.caminho, item]),
2394
+ ...indexRust.simbolos.map((item) => [item.caminho, item]),
2395
+ ...indexCpp.simbolos.map((item) => [item.caminho, item]),
2396
+ ]);
2397
+ const todosRecursos = [
2398
+ ...indexTs.recursos,
2399
+ ...indexPy.recursos,
2400
+ ...indexDart.recursos,
2401
+ ...indexDotnet.recursos,
2402
+ ...indexJava.recursos,
2403
+ ...indexGo.recursos,
2404
+ ...indexRust.recursos,
2405
+ ...indexCpp.recursos,
2406
+ ...indexPersistencia.recursos,
2407
+ ];
2408
+ const mapaRecursos = construirMapaRecursos(todosRecursos);
2409
+ const todasRotasIndexadas = [
2410
+ ...indexTs.rotas,
2411
+ ...indexPy.rotas,
2412
+ ...indexDart.rotas,
2413
+ ...indexDotnet.rotas,
2414
+ ...indexJava.rotas,
2415
+ ...indexGo.rotas,
2416
+ ...indexRust.rotas,
2417
+ ];
2418
+ const todosArquivosConhecidos = [...new Set([
2419
+ ...todosSimbolos.map((item) => item.arquivo),
2420
+ ...todasRotasIndexadas.map((item) => item.arquivo),
2421
+ ...todosRecursos.map((item) => item.arquivo),
2422
+ ])].sort((a, b) => a.localeCompare(b, "pt-BR"));
2423
+ const implsValidos = [];
2424
+ const implsQuebrados = [];
2425
+ const vinculosValidos = [];
2426
+ const vinculosQuebrados = [];
2427
+ const rotasDivergentes = [];
2428
+ const recursosValidos = [];
2429
+ const recursosDivergentes = [];
2430
+ const diagnosticos = [];
2431
+ const tasksResumo = [];
2432
+ const taskPorChave = new Map();
2433
+ const guardrailsPorTask = new Map();
2434
+ const resumoVinculosPorTask = new Map();
2435
+ for (const item of contexto.modulosSelecionados) {
2436
+ const ir = item.resultado.ir;
2437
+ if (!ir) {
1443
2438
  continue;
1444
2439
  }
1445
- const guardrails = guardrailsPorTask.get(`${ir.nome}:${route.task}`);
1446
- if (guardrails) {
1447
- guardrails.publica = true;
1448
- guardrails.auth = guardrails.auth || route.auth.explicita;
1449
- guardrails.authz = guardrails.authz || route.authz.explicita;
1450
- guardrails.dados = guardrails.dados || route.dados.explicita;
1451
- guardrails.audit = guardrails.audit || route.audit.explicita;
1452
- guardrails.segredos = guardrails.segredos || route.segredos.explicita;
1453
- guardrails.forbidden = guardrails.forbidden || route.forbidden.explicita;
1454
- guardrails.dadosSensiveis = guardrails.dadosSensiveis || Boolean(route.dados.classificacaoPadrao && ["pii", "financeiro", "credencial", "segredo"].includes(route.dados.classificacaoPadrao)
1455
- || route.dados.campos.some((campo) => ["pii", "financeiro", "credencial", "segredo"].includes(campo.classificacao)));
1456
- guardrails.efeitoPrivilegiado = guardrails.efeitoPrivilegiado || route.efeitosPublicos.some((efeito) => ["db.read", "db.write", "queue.publish", "queue.consume", "fs.read", "fs.write", "network.egress", "secret.read", "shell.exec"].includes(efeito.categoria)
1457
- || ["alta", "critica"].includes(efeito.criticidade ?? ""));
1458
- guardrails.exigeSegredos = guardrails.exigeSegredos || route.efeitosPublicos.some((efeito) => efeito.categoria === "secret.read")
1459
- || Boolean(route.dados.classificacaoPadrao && ["credencial", "segredo"].includes(route.dados.classificacaoPadrao)
1460
- || route.dados.campos.some((campo) => ["credencial", "segredo"].includes(campo.classificacao)));
1461
- }
1462
- }
1463
- for (const superficie of ir.superficies) {
1464
- if (!superficie.task || superficie.perfilCompatibilidade !== "publico") {
1465
- continue;
2440
+ const superficiesPorChave = new Map(ir.superficies.map((superficie) => [`${superficie.tipo}:${superficie.nome}`, superficie]));
2441
+ for (const task of ir.tasks) {
2442
+ guardrailsPorTask.set(`${ir.nome}:${task.nome}`, {
2443
+ publica: false,
2444
+ sensivel: calcularRiscoOperacional(task) === "alto",
2445
+ auth: task.auth.explicita,
2446
+ authz: task.authz.explicita,
2447
+ dados: task.dados.explicita,
2448
+ audit: task.audit.explicita,
2449
+ segredos: task.segredos.explicita,
2450
+ forbidden: task.forbidden.explicita,
2451
+ dadosSensiveis: Boolean(task.dados.classificacaoPadrao && ["pii", "financeiro", "credencial", "segredo"].includes(task.dados.classificacaoPadrao)
2452
+ || task.dados.campos.some((campo) => ["pii", "financeiro", "credencial", "segredo"].includes(campo.classificacao))),
2453
+ efeitoPrivilegiado: task.efeitosEstruturados.some((efeito) => ["db.read", "db.write", "queue.publish", "queue.consume", "fs.read", "fs.write", "network.egress", "secret.read", "shell.exec"].includes(efeito.categoria)
2454
+ || ["alta", "critica"].includes(efeito.criticidade ?? "")),
2455
+ exigeSegredos: task.efeitosEstruturados.some((efeito) => efeito.categoria === "secret.read")
2456
+ || Boolean(task.dados.classificacaoPadrao && ["credencial", "segredo"].includes(task.dados.classificacaoPadrao)
2457
+ || task.dados.campos.some((campo) => ["credencial", "segredo"].includes(campo.classificacao))),
2458
+ });
1466
2459
  }
1467
- const guardrails = guardrailsPorTask.get(`${ir.nome}:${superficie.task}`);
1468
- if (guardrails) {
1469
- guardrails.publica = true;
1470
- guardrails.auth = guardrails.auth || superficie.auth.explicita;
1471
- guardrails.authz = guardrails.authz || superficie.authz.explicita;
1472
- guardrails.dados = guardrails.dados || superficie.dados.explicita;
1473
- guardrails.audit = guardrails.audit || superficie.audit.explicita;
1474
- guardrails.segredos = guardrails.segredos || superficie.segredos.explicita;
1475
- guardrails.forbidden = guardrails.forbidden || superficie.forbidden.explicita;
1476
- guardrails.dadosSensiveis = guardrails.dadosSensiveis || Boolean(superficie.dados.classificacaoPadrao && ["pii", "financeiro", "credencial", "segredo"].includes(superficie.dados.classificacaoPadrao)
1477
- || superficie.dados.campos.some((campo) => ["pii", "financeiro", "credencial", "segredo"].includes(campo.classificacao)));
1478
- guardrails.efeitoPrivilegiado = guardrails.efeitoPrivilegiado || superficie.effects.some((efeito) => ["db.read", "db.write", "queue.publish", "queue.consume", "fs.read", "fs.write", "network.egress", "secret.read", "shell.exec"].includes(efeito.categoria)
1479
- || ["alta", "critica"].includes(efeito.criticidade ?? ""));
1480
- guardrails.exigeSegredos = guardrails.exigeSegredos || superficie.effects.some((efeito) => efeito.categoria === "secret.read")
1481
- || Boolean(superficie.dados.classificacaoPadrao && ["credencial", "segredo"].includes(superficie.dados.classificacaoPadrao)
1482
- || superficie.dados.campos.some((campo) => ["credencial", "segredo"].includes(campo.classificacao)));
2460
+ for (const route of ir.routes) {
2461
+ if (!route.task || route.perfilCompatibilidade !== "publico") {
2462
+ continue;
2463
+ }
2464
+ const guardrails = guardrailsPorTask.get(`${ir.nome}:${route.task}`);
2465
+ if (guardrails) {
2466
+ guardrails.publica = true;
2467
+ guardrails.auth = guardrails.auth || route.auth.explicita;
2468
+ guardrails.authz = guardrails.authz || route.authz.explicita;
2469
+ guardrails.dados = guardrails.dados || route.dados.explicita;
2470
+ guardrails.audit = guardrails.audit || route.audit.explicita;
2471
+ guardrails.segredos = guardrails.segredos || route.segredos.explicita;
2472
+ guardrails.forbidden = guardrails.forbidden || route.forbidden.explicita;
2473
+ guardrails.dadosSensiveis = guardrails.dadosSensiveis || Boolean(route.dados.classificacaoPadrao && ["pii", "financeiro", "credencial", "segredo"].includes(route.dados.classificacaoPadrao)
2474
+ || route.dados.campos.some((campo) => ["pii", "financeiro", "credencial", "segredo"].includes(campo.classificacao)));
2475
+ guardrails.efeitoPrivilegiado = guardrails.efeitoPrivilegiado || route.efeitosPublicos.some((efeito) => ["db.read", "db.write", "queue.publish", "queue.consume", "fs.read", "fs.write", "network.egress", "secret.read", "shell.exec"].includes(efeito.categoria)
2476
+ || ["alta", "critica"].includes(efeito.criticidade ?? ""));
2477
+ guardrails.exigeSegredos = guardrails.exigeSegredos || route.efeitosPublicos.some((efeito) => efeito.categoria === "secret.read")
2478
+ || Boolean(route.dados.classificacaoPadrao && ["credencial", "segredo"].includes(route.dados.classificacaoPadrao)
2479
+ || route.dados.campos.some((campo) => ["credencial", "segredo"].includes(campo.classificacao)));
2480
+ }
1483
2481
  }
1484
- }
1485
- for (const task of ir.tasks) {
1486
- taskPorChave.set(`${ir.nome}:${task.nome}`, task);
1487
- let validos = 0;
1488
- let quebrados = 0;
1489
- const arquivosReferenciados = new Set();
1490
- const simbolosReferenciados = new Set();
1491
- const candidatosTask = new Map();
1492
- if (task.implementacoesExternas.length === 0) {
1493
- for (const candidato of sugerirCandidatosParaTaskSemImpl(todosSimbolos, task.nome)) {
1494
- candidatosTask.set(`${candidato.origem}:${candidato.caminho}:${candidato.arquivo}:${candidato.simbolo}`, candidato);
2482
+ for (const superficie of ir.superficies) {
2483
+ if (!superficie.task || superficie.perfilCompatibilidade !== "publico") {
2484
+ continue;
1495
2485
  }
1496
- diagnosticos.push({
1497
- tipo: "task_sem_impl",
2486
+ const guardrails = guardrailsPorTask.get(`${ir.nome}:${superficie.task}`);
2487
+ if (guardrails) {
2488
+ guardrails.publica = true;
2489
+ guardrails.auth = guardrails.auth || superficie.auth.explicita;
2490
+ guardrails.authz = guardrails.authz || superficie.authz.explicita;
2491
+ guardrails.dados = guardrails.dados || superficie.dados.explicita;
2492
+ guardrails.audit = guardrails.audit || superficie.audit.explicita;
2493
+ guardrails.segredos = guardrails.segredos || superficie.segredos.explicita;
2494
+ guardrails.forbidden = guardrails.forbidden || superficie.forbidden.explicita;
2495
+ guardrails.dadosSensiveis = guardrails.dadosSensiveis || Boolean(superficie.dados.classificacaoPadrao && ["pii", "financeiro", "credencial", "segredo"].includes(superficie.dados.classificacaoPadrao)
2496
+ || superficie.dados.campos.some((campo) => ["pii", "financeiro", "credencial", "segredo"].includes(campo.classificacao)));
2497
+ guardrails.efeitoPrivilegiado = guardrails.efeitoPrivilegiado || superficie.effects.some((efeito) => ["db.read", "db.write", "queue.publish", "queue.consume", "fs.read", "fs.write", "network.egress", "secret.read", "shell.exec"].includes(efeito.categoria)
2498
+ || ["alta", "critica"].includes(efeito.criticidade ?? ""));
2499
+ guardrails.exigeSegredos = guardrails.exigeSegredos || superficie.effects.some((efeito) => efeito.categoria === "secret.read")
2500
+ || Boolean(superficie.dados.classificacaoPadrao && ["credencial", "segredo"].includes(superficie.dados.classificacaoPadrao)
2501
+ || superficie.dados.campos.some((campo) => ["credencial", "segredo"].includes(campo.classificacao)));
2502
+ }
2503
+ }
2504
+ for (const task of ir.tasks) {
2505
+ taskPorChave.set(`${ir.nome}:${task.nome}`, task);
2506
+ let validos = 0;
2507
+ let quebrados = 0;
2508
+ const arquivosReferenciados = new Set();
2509
+ const simbolosReferenciados = new Set();
2510
+ const candidatosTask = new Map();
2511
+ if (task.implementacoesExternas.length === 0) {
2512
+ for (const candidato of sugerirCandidatosParaTaskSemImpl(todosSimbolos, task.nome)) {
2513
+ candidatosTask.set(`${candidato.origem}:${candidato.caminho}:${candidato.arquivo}:${candidato.simbolo}`, candidato);
2514
+ }
2515
+ diagnosticos.push({
2516
+ tipo: "task_sem_impl",
2517
+ modulo: ir.nome,
2518
+ task: task.nome,
2519
+ mensagem: `Task "${task.nome}" ainda nao foi ligada a nenhuma implementacao externa.`,
2520
+ });
2521
+ }
2522
+ for (const impl of task.implementacoesExternas) {
2523
+ const resolvido = mapaImpl.get(impl.caminho);
2524
+ const registro = {
2525
+ modulo: ir.nome,
2526
+ task: task.nome,
2527
+ origem: impl.origem,
2528
+ caminho: impl.caminho,
2529
+ arquivo: resolvido?.arquivo,
2530
+ simbolo: resolvido?.simbolo,
2531
+ caminhoResolvido: resolvido?.caminho,
2532
+ status: resolvido ? "resolvido" : "quebrado",
2533
+ };
2534
+ if (resolvido) {
2535
+ arquivosReferenciados.add(resolvido.arquivo);
2536
+ simbolosReferenciados.add(resolvido.simbolo);
2537
+ implsValidos.push(registro);
2538
+ validos += 1;
2539
+ }
2540
+ else {
2541
+ registro.candidatos = sugerirCandidatosParaImpl(todosSimbolos, impl.origem, impl.caminho);
2542
+ for (const candidato of registro.candidatos) {
2543
+ candidatosTask.set(`${candidato.origem}:${candidato.caminho}:${candidato.arquivo}:${candidato.simbolo}`, candidato);
2544
+ }
2545
+ implsQuebrados.push(registro);
2546
+ quebrados += 1;
2547
+ diagnosticos.push({
2548
+ tipo: "impl_quebrado",
2549
+ modulo: ir.nome,
2550
+ task: task.nome,
2551
+ mensagem: `Implementacao externa "${impl.origem}:${impl.caminho}" nao foi encontrada nos diretorios de codigo vivos.`,
2552
+ });
2553
+ }
2554
+ }
2555
+ tasksResumo.push({
1498
2556
  modulo: ir.nome,
1499
2557
  task: task.nome,
1500
- mensagem: `Task "${task.nome}" ainda nao foi ligada a nenhuma implementacao externa.`,
2558
+ impls: task.implementacoesExternas.length,
2559
+ implsValidos: validos,
2560
+ implsQuebrados: quebrados,
2561
+ semImplementacao: task.implementacoesExternas.length === 0,
2562
+ scoreSemantico: 0,
2563
+ confiancaVinculo: "baixa",
2564
+ riscoOperacional: "baixo",
2565
+ lacunas: [],
2566
+ arquivosReferenciados: [...arquivosReferenciados].sort((a, b) => a.localeCompare(b, "pt-BR")),
2567
+ arquivosProvaveisEditar: [],
2568
+ simbolosReferenciados: [...simbolosReferenciados].sort((a, b) => a.localeCompare(b, "pt-BR")),
2569
+ candidatosImpl: ordenarCandidatos([...candidatosTask.values()]).slice(0, 5),
2570
+ checksSugeridos: [],
1501
2571
  });
2572
+ for (const recursoEsperado of extrairRecursosEsperados(task, ir)) {
2573
+ const resolvido = resolverRecursoEsperado(mapaRecursos, recursoEsperado, arquivosReferenciados);
2574
+ const registro = {
2575
+ modulo: ir.nome,
2576
+ task: task.nome,
2577
+ categoria: recursoEsperado.categoria,
2578
+ alvo: recursoEsperado.alvo,
2579
+ arquivo: resolvido?.arquivo ?? "",
2580
+ origem: resolvido?.origem ?? recursoEsperado.origem ?? "firebase",
2581
+ tipo: resolvido?.tipo ?? recursoEsperado.tiposAceitos[0] ?? "query",
2582
+ status: resolvido ? "resolvido" : "divergente",
2583
+ };
2584
+ if (resolvido) {
2585
+ registro.arquivo = resolvido.arquivo;
2586
+ recursosValidos.push(registro);
2587
+ }
2588
+ else {
2589
+ recursosDivergentes.push(registro);
2590
+ const escopo = recursoEsperado.origem ? `${recursoEsperado.origem}` : "persistencia declarada";
2591
+ diagnosticos.push({
2592
+ tipo: "recurso_divergente",
2593
+ modulo: ir.nome,
2594
+ task: task.nome,
2595
+ mensagem: `Recurso vivo "${recursoEsperado.alvo}" nao foi encontrado no codigo legado para ${escopo}.`,
2596
+ });
2597
+ }
2598
+ }
1502
2599
  }
1503
- for (const impl of task.implementacoesExternas) {
1504
- const resolvido = mapaImpl.get(impl.caminho);
1505
- const registro = {
1506
- modulo: ir.nome,
1507
- task: task.nome,
1508
- origem: impl.origem,
1509
- caminho: impl.caminho,
1510
- arquivo: resolvido?.arquivo,
1511
- simbolo: resolvido?.simbolo,
1512
- caminhoResolvido: resolvido?.caminho,
1513
- status: resolvido ? "resolvido" : "quebrado",
1514
- };
1515
- if (resolvido) {
1516
- arquivosReferenciados.add(resolvido.arquivo);
1517
- simbolosReferenciados.add(resolvido.simbolo);
1518
- implsValidos.push(registro);
1519
- validos += 1;
2600
+ for (const route of ir.routes) {
2601
+ const taskAssociada = ir.tasks.find((task) => task.nome === route.task);
2602
+ const esperadas = escolherRotasEsperadas(taskAssociada ?? {
2603
+ nome: "",
2604
+ input: [],
2605
+ output: [],
2606
+ rules: [],
2607
+ regrasEstruturadas: [],
2608
+ effects: [],
2609
+ efeitosEstruturados: [],
2610
+ implementacoesExternas: [],
2611
+ vinculos: [],
2612
+ execucao: {
2613
+ idempotencia: false,
2614
+ timeout: "padrao",
2615
+ retry: "nenhum",
2616
+ compensacao: "nenhuma",
2617
+ criticidadeOperacional: "media",
2618
+ explicita: false,
2619
+ },
2620
+ auth: {
2621
+ explicita: false,
2622
+ },
2623
+ authz: {
2624
+ explicita: false,
2625
+ papeis: [],
2626
+ escopos: [],
2627
+ },
2628
+ dados: {
2629
+ explicita: false,
2630
+ campos: [],
2631
+ },
2632
+ audit: {
2633
+ explicita: false,
2634
+ },
2635
+ segredos: {
2636
+ explicita: false,
2637
+ itens: [],
2638
+ },
2639
+ forbidden: {
2640
+ explicita: false,
2641
+ regras: [],
2642
+ },
2643
+ guarantees: [],
2644
+ garantiasEstruturadas: [],
2645
+ errors: {},
2646
+ errosDetalhados: [],
2647
+ perfilCompatibilidade: "interno",
2648
+ resumoAgente: {
2649
+ riscos: [],
2650
+ checks: [],
2651
+ entidadesAfetadas: [],
2652
+ superficiesPublicas: [],
2653
+ mutacoesPrevistas: [],
2654
+ },
2655
+ tests: [],
2656
+ }, contexto.fontesLegado);
2657
+ if (!esperadas.length || !route.metodo || !route.caminho) {
2658
+ continue;
1520
2659
  }
1521
- else {
1522
- registro.candidatos = sugerirCandidatosParaImpl(todosSimbolos, impl.origem, impl.caminho);
1523
- for (const candidato of registro.candidatos) {
1524
- candidatosTask.set(`${candidato.origem}:${candidato.caminho}:${candidato.arquivo}:${candidato.simbolo}`, candidato);
1525
- }
1526
- implsQuebrados.push(registro);
1527
- quebrados += 1;
2660
+ const encontradas = todasRotasIndexadas.filter((rotaResolvida) => rotaResolvida.origem !== "nextjs-consumer"
2661
+ && rotaResolvida.origem !== "react-vite-consumer"
2662
+ && rotaResolvida.origem !== "angular-consumer"
2663
+ && rotaResolvida.origem !== "flutter-consumer"
2664
+ && esperadas.includes(rotaResolvida.origem));
2665
+ const combina = encontradas.some((rotaResolvida) => rotaResolvida.metodo === route.metodo
2666
+ && normalizarCaminhoRota(rotaResolvida.caminho) === normalizarCaminhoRota(route.caminho));
2667
+ if (!combina) {
2668
+ const registro = {
2669
+ modulo: ir.nome,
2670
+ route: route.nome,
2671
+ metodo: route.metodo,
2672
+ caminho: route.caminho,
2673
+ motivo: `Nenhuma rota publica ${route.metodo} ${route.caminho} foi encontrada no codigo legado para o framework esperado.`,
2674
+ };
2675
+ rotasDivergentes.push(registro);
1528
2676
  diagnosticos.push({
1529
- tipo: "impl_quebrado",
2677
+ tipo: "rota_divergente",
1530
2678
  modulo: ir.nome,
1531
- task: task.nome,
1532
- mensagem: `Implementacao externa "${impl.origem}:${impl.caminho}" nao foi encontrada nos diretorios de codigo vivos.`,
2679
+ route: route.nome,
2680
+ mensagem: registro.motivo,
1533
2681
  });
1534
2682
  }
1535
2683
  }
1536
- tasksResumo.push({
1537
- modulo: ir.nome,
1538
- task: task.nome,
1539
- impls: task.implementacoesExternas.length,
1540
- implsValidos: validos,
1541
- implsQuebrados: quebrados,
1542
- semImplementacao: task.implementacoesExternas.length === 0,
1543
- scoreSemantico: 0,
1544
- confiancaVinculo: "baixa",
1545
- riscoOperacional: "baixo",
1546
- lacunas: [],
1547
- arquivosReferenciados: [...arquivosReferenciados].sort((a, b) => a.localeCompare(b, "pt-BR")),
1548
- arquivosProvaveisEditar: [],
1549
- simbolosReferenciados: [...simbolosReferenciados].sort((a, b) => a.localeCompare(b, "pt-BR")),
1550
- candidatosImpl: ordenarCandidatos([...candidatosTask.values()]).slice(0, 5),
1551
- checksSugeridos: [],
1552
- });
1553
- for (const recursoEsperado of extrairRecursosEsperados(task)) {
1554
- const resolvido = mapaRecursos.get(recursoEsperado.alvo);
2684
+ for (const itemVinculo of coletarVinculosIr(ir)) {
1555
2685
  const registro = {
1556
2686
  modulo: ir.nome,
1557
- task: task.nome,
1558
- categoria: recursoEsperado.categoria,
1559
- alvo: recursoEsperado.alvo,
1560
- arquivo: resolvido?.arquivo ?? "",
1561
- origem: "firebase",
1562
- tipo: "colecao",
1563
- status: resolvido ? "resolvido" : "divergente",
2687
+ donoTipo: itemVinculo.donoTipo,
2688
+ dono: itemVinculo.dono,
2689
+ tipo: itemVinculo.vinculo.tipo,
2690
+ valor: itemVinculo.vinculo.valor,
2691
+ status: "nao_encontrado",
2692
+ confianca: "baixa",
1564
2693
  };
1565
- if (resolvido) {
1566
- registro.arquivo = resolvido.arquivo;
1567
- recursosValidos.push(registro);
2694
+ const arquivoDeclarado = itemVinculo.vinculo.arquivo ?? (itemVinculo.vinculo.tipo === "arquivo" ? itemVinculo.vinculo.valor : undefined);
2695
+ const simboloDeclarado = itemVinculo.vinculo.simbolo ?? (itemVinculo.vinculo.tipo === "simbolo" ? itemVinculo.vinculo.valor : undefined);
2696
+ const recursoDeclarado = itemVinculo.vinculo.recurso ?? (["recurso", "tabela", "fila", "cache", "storage"].includes(itemVinculo.vinculo.tipo) ? itemVinculo.vinculo.valor : undefined);
2697
+ const superficieDeclarada = itemVinculo.vinculo.superficie ?? (["superficie", "rota", "worker", "cron", "webhook", "evento", "policy", "fila", "cache", "storage"].includes(itemVinculo.vinculo.tipo) ? itemVinculo.vinculo.valor : undefined);
2698
+ if (simboloDeclarado) {
2699
+ const resolucaoSimbolo = escolherSimboloPorVinculo(todosSimbolos, mapaImpl, simboloDeclarado);
2700
+ registro.status = resolucaoSimbolo.status;
2701
+ registro.confianca = resolucaoSimbolo.confianca;
2702
+ registro.arquivo = resolucaoSimbolo.simbolo?.arquivo;
2703
+ registro.simbolo = resolucaoSimbolo.simbolo?.simbolo;
2704
+ }
2705
+ else if (arquivoDeclarado) {
2706
+ const resolucaoArquivo = escolherArquivoPorVinculo(todosArquivosConhecidos, arquivoDeclarado);
2707
+ registro.status = resolucaoArquivo.status;
2708
+ registro.confianca = resolucaoArquivo.confianca;
2709
+ registro.arquivo = resolucaoArquivo.arquivo;
2710
+ }
2711
+ else if (recursoDeclarado) {
2712
+ const recurso = resolverRecursoEsperado(mapaRecursos, {
2713
+ categoria: "persistencia",
2714
+ alvo: recursoDeclarado,
2715
+ tiposAceitos: [],
2716
+ nomes: [recursoDeclarado],
2717
+ });
2718
+ if (recurso) {
2719
+ registro.status = "resolvido";
2720
+ registro.confianca = "alta";
2721
+ registro.arquivo = recurso.arquivo;
2722
+ }
2723
+ }
2724
+ else if (superficieDeclarada) {
2725
+ const rota = todasRotasIndexadas.find((rotaResolvida) => normalizarCaminhoRota(rotaResolvida.caminho) === normalizarCaminhoRota(superficieDeclarada));
2726
+ if (rota) {
2727
+ registro.status = "resolvido";
2728
+ registro.confianca = "alta";
2729
+ registro.arquivo = rota.arquivo;
2730
+ registro.simbolo = rota.simbolo;
2731
+ }
2732
+ else {
2733
+ const resolucaoArquivo = escolherArquivoPorVinculo(todosArquivosConhecidos, superficieDeclarada);
2734
+ registro.status = resolucaoArquivo.status;
2735
+ registro.confianca = resolucaoArquivo.confianca;
2736
+ registro.arquivo = resolucaoArquivo.arquivo;
2737
+ }
1568
2738
  }
1569
2739
  else {
1570
- recursosDivergentes.push(registro);
2740
+ const resolucaoArquivo = escolherArquivoPorVinculo(todosArquivosConhecidos, itemVinculo.vinculo.valor);
2741
+ registro.status = resolucaoArquivo.status;
2742
+ registro.confianca = resolucaoArquivo.confianca;
2743
+ registro.arquivo = resolucaoArquivo.arquivo;
2744
+ }
2745
+ if (registro.status === "nao_encontrado" && itemVinculo.donoTipo === "superficie") {
2746
+ const superficie = superficiesPorChave.get(itemVinculo.dono);
2747
+ const ancora = superficie
2748
+ ? encontrarAncoraSuperficie(ir, superficie, todosSimbolos, mapaImpl, todosArquivosConhecidos)
2749
+ : undefined;
2750
+ if (ancora) {
2751
+ registro.status = "parcial";
2752
+ registro.confianca = ancora.confianca === "alta" ? "media" : ancora.confianca;
2753
+ registro.arquivo = registro.arquivo ?? ancora.arquivo;
2754
+ registro.simbolo = registro.simbolo ?? ancora.simbolo;
2755
+ }
2756
+ }
2757
+ if (registro.status === "nao_encontrado") {
2758
+ vinculosQuebrados.push(registro);
1571
2759
  diagnosticos.push({
1572
- tipo: "recurso_divergente",
2760
+ tipo: "vinculo_quebrado",
1573
2761
  modulo: ir.nome,
1574
- task: task.nome,
1575
- mensagem: `Recurso vivo "${recursoEsperado.alvo}" nao foi encontrado nos bridges/configuracoes Firebase do codigo legado.`,
2762
+ mensagem: `Vinculo ${registro.tipo}="${registro.valor}" de ${registro.donoTipo} "${registro.dono}" nao foi resolvido no codigo vivo.`,
2763
+ ...(itemVinculo.donoTipo === "task" ? { task: itemVinculo.dono } : itemVinculo.donoTipo === "route" ? { route: itemVinculo.dono } : {}),
1576
2764
  });
1577
2765
  }
2766
+ else {
2767
+ vinculosValidos.push(registro);
2768
+ }
2769
+ if (itemVinculo.donoTipo === "task") {
2770
+ const chaveTask = `${ir.nome}:${itemVinculo.dono}`;
2771
+ const resumo = resumoVinculosPorTask.get(chaveTask) ?? {
2772
+ validos: 0,
2773
+ quebrados: 0,
2774
+ arquivos: new Set(),
2775
+ };
2776
+ if (registro.status === "nao_encontrado") {
2777
+ resumo.quebrados += 1;
2778
+ }
2779
+ else {
2780
+ resumo.validos += 1;
2781
+ }
2782
+ if (registro.arquivo) {
2783
+ resumo.arquivos.add(registro.arquivo);
2784
+ }
2785
+ resumoVinculosPorTask.set(chaveTask, resumo);
2786
+ }
1578
2787
  }
1579
2788
  }
1580
- for (const route of ir.routes) {
1581
- const taskAssociada = ir.tasks.find((task) => task.nome === route.task);
1582
- const esperadas = escolherRotasEsperadas(taskAssociada ?? {
1583
- nome: "",
1584
- input: [],
1585
- output: [],
1586
- rules: [],
1587
- regrasEstruturadas: [],
1588
- effects: [],
1589
- efeitosEstruturados: [],
1590
- implementacoesExternas: [],
1591
- vinculos: [],
1592
- execucao: {
1593
- idempotencia: false,
1594
- timeout: "padrao",
1595
- retry: "nenhum",
1596
- compensacao: "nenhuma",
1597
- criticidadeOperacional: "media",
1598
- explicita: false,
1599
- },
1600
- auth: {
1601
- explicita: false,
1602
- },
1603
- authz: {
1604
- explicita: false,
1605
- papeis: [],
1606
- escopos: [],
1607
- },
1608
- dados: {
1609
- explicita: false,
1610
- campos: [],
1611
- },
1612
- audit: {
1613
- explicita: false,
1614
- },
1615
- segredos: {
1616
- explicita: false,
1617
- itens: [],
1618
- },
1619
- forbidden: {
1620
- explicita: false,
1621
- regras: [],
1622
- },
1623
- guarantees: [],
1624
- garantiasEstruturadas: [],
1625
- errors: {},
1626
- errosDetalhados: [],
1627
- perfilCompatibilidade: "interno",
1628
- resumoAgente: {
1629
- riscos: [],
1630
- checks: [],
1631
- entidadesAfetadas: [],
1632
- superficiesPublicas: [],
1633
- mutacoesPrevistas: [],
1634
- },
1635
- tests: [],
1636
- }, contexto.fontesLegado);
1637
- if (!esperadas.length || !route.metodo || !route.caminho) {
2789
+ for (const resumo of tasksResumo) {
2790
+ const chaveTask = `${resumo.modulo}:${resumo.task}`;
2791
+ const task = taskPorChave.get(chaveTask);
2792
+ const guardrails = guardrailsPorTask.get(chaveTask) ?? {
2793
+ publica: false,
2794
+ sensivel: false,
2795
+ auth: false,
2796
+ authz: false,
2797
+ dados: false,
2798
+ audit: false,
2799
+ segredos: false,
2800
+ forbidden: false,
2801
+ dadosSensiveis: false,
2802
+ efeitoPrivilegiado: false,
2803
+ exigeSegredos: false,
2804
+ };
2805
+ const resumoVinculos = resumoVinculosPorTask.get(chaveTask) ?? {
2806
+ validos: 0,
2807
+ quebrados: 0,
2808
+ arquivos: new Set(),
2809
+ };
2810
+ if (!task) {
1638
2811
  continue;
1639
2812
  }
1640
- const encontradas = todasRotasIndexadas.filter((rotaResolvida) => rotaResolvida.origem !== "nextjs-consumer"
1641
- && rotaResolvida.origem !== "react-vite-consumer"
1642
- && rotaResolvida.origem !== "angular-consumer"
1643
- && rotaResolvida.origem !== "flutter-consumer"
1644
- && esperadas.includes(rotaResolvida.origem));
1645
- const combina = encontradas.some((rotaResolvida) => rotaResolvida.metodo === route.metodo
1646
- && normalizarCaminhoRota(rotaResolvida.caminho) === normalizarCaminhoRota(route.caminho));
1647
- if (!combina) {
1648
- const registro = {
1649
- modulo: ir.nome,
1650
- route: route.nome,
1651
- metodo: route.metodo,
1652
- caminho: route.caminho,
1653
- motivo: `Nenhuma rota publica ${route.metodo} ${route.caminho} foi encontrada no codigo legado para o framework esperado.`,
1654
- };
1655
- rotasDivergentes.push(registro);
2813
+ resumo.confiancaVinculo = calcularConfiancaTask(task, resumo.implsValidos, resumo.implsQuebrados, resumoVinculos.validos, resumoVinculos.quebrados);
2814
+ resumo.riscoOperacional = calcularRiscoOperacional(task);
2815
+ resumo.lacunas = resumirLacunasTask(task, resumo.semImplementacao, resumo.implsQuebrados, resumoVinculos.quebrados, guardrails);
2816
+ resumo.scoreSemantico = calcularScoreTask(task, resumo.implsValidos, resumo.implsQuebrados, resumoVinculos.validos, resumoVinculos.quebrados, resumo.semImplementacao);
2817
+ resumo.arquivosProvaveisEditar = [...new Set([
2818
+ ...resumo.arquivosReferenciados,
2819
+ ...resumo.candidatosImpl.map((candidato) => candidato.arquivo),
2820
+ ...resumoVinculos.arquivos,
2821
+ ])].sort((a, b) => a.localeCompare(b, "pt-BR"));
2822
+ resumo.checksSugeridos = [...new Set([
2823
+ ...task.resumoAgente.checks,
2824
+ resumo.riscoOperacional !== "baixo" ? "revisar efeitos operacionais" : "",
2825
+ resumo.lacunas.includes("vinculo_quebrado") ? "corrigir vinculos rastreaveis" : "",
2826
+ resumo.lacunas.some((lacuna) => ["superficie_publica_sem_execucao", "execucao_critica_sem_bloco", "rastreabilidade_fraca"].includes(lacuna))
2827
+ ? "endurecer execucao e rastreabilidade para producao"
2828
+ : "",
2829
+ resumo.lacunas.some((lacuna) => ["auth_ausente", "authz_frouxa", "dados_nao_classificados", "audit_ausente", "segredo_sem_governanca", "proibicoes_ausentes"].includes(lacuna))
2830
+ ? "explicitar contratos de seguranca semantica"
2831
+ : "",
2832
+ ].filter(Boolean))];
2833
+ if (resumo.lacunas.includes("superficie_publica_sem_execucao")) {
1656
2834
  diagnosticos.push({
1657
- tipo: "rota_divergente",
1658
- modulo: ir.nome,
1659
- route: route.nome,
1660
- mensagem: registro.motivo,
2835
+ tipo: "seguranca_frouxa",
2836
+ modulo: resumo.modulo,
2837
+ task: resumo.task,
2838
+ mensagem: `Task "${resumo.task}" alimenta superficie publica, mas ainda depende de execucao implicita.`,
1661
2839
  });
1662
2840
  }
1663
- }
1664
- for (const itemVinculo of coletarVinculosIr(ir)) {
1665
- const registro = {
1666
- modulo: ir.nome,
1667
- donoTipo: itemVinculo.donoTipo,
1668
- dono: itemVinculo.dono,
1669
- tipo: itemVinculo.vinculo.tipo,
1670
- valor: itemVinculo.vinculo.valor,
1671
- status: "nao_encontrado",
1672
- confianca: "baixa",
1673
- };
1674
- const arquivoDeclarado = itemVinculo.vinculo.arquivo ?? (itemVinculo.vinculo.tipo === "arquivo" ? itemVinculo.vinculo.valor : undefined);
1675
- const simboloDeclarado = itemVinculo.vinculo.simbolo ?? (itemVinculo.vinculo.tipo === "simbolo" ? itemVinculo.vinculo.valor : undefined);
1676
- const recursoDeclarado = itemVinculo.vinculo.recurso ?? (["recurso", "tabela", "fila", "cache", "storage"].includes(itemVinculo.vinculo.tipo) ? itemVinculo.vinculo.valor : undefined);
1677
- const superficieDeclarada = itemVinculo.vinculo.superficie ?? (["superficie", "rota", "worker", "cron", "webhook", "evento", "policy", "fila", "cache", "storage"].includes(itemVinculo.vinculo.tipo) ? itemVinculo.vinculo.valor : undefined);
1678
- if (simboloDeclarado) {
1679
- const resolucaoSimbolo = escolherSimboloPorVinculo(todosSimbolos, mapaImpl, simboloDeclarado);
1680
- registro.status = resolucaoSimbolo.status;
1681
- registro.confianca = resolucaoSimbolo.confianca;
1682
- registro.arquivo = resolucaoSimbolo.simbolo?.arquivo;
1683
- registro.simbolo = resolucaoSimbolo.simbolo?.simbolo;
1684
- }
1685
- else if (arquivoDeclarado) {
1686
- const resolucaoArquivo = escolherArquivoPorVinculo(todosArquivosConhecidos, arquivoDeclarado);
1687
- registro.status = resolucaoArquivo.status;
1688
- registro.confianca = resolucaoArquivo.confianca;
1689
- registro.arquivo = resolucaoArquivo.arquivo;
1690
- }
1691
- else if (recursoDeclarado) {
1692
- const recurso = mapaRecursos.get(recursoDeclarado);
1693
- if (recurso) {
1694
- registro.status = "resolvido";
1695
- registro.confianca = "alta";
1696
- registro.arquivo = recurso.arquivo;
1697
- }
2841
+ if (resumo.lacunas.includes("execucao_critica_sem_bloco")) {
2842
+ diagnosticos.push({
2843
+ tipo: "seguranca_frouxa",
2844
+ modulo: resumo.modulo,
2845
+ task: resumo.task,
2846
+ mensagem: `Task "${resumo.task}" opera com risco alto, mas ainda nao declarou execucao explicita.`,
2847
+ });
1698
2848
  }
1699
- else if (superficieDeclarada) {
1700
- const rota = todasRotasIndexadas.find((rotaResolvida) => normalizarCaminhoRota(rotaResolvida.caminho) === normalizarCaminhoRota(superficieDeclarada));
1701
- if (rota) {
1702
- registro.status = "resolvido";
1703
- registro.confianca = "alta";
1704
- registro.arquivo = rota.arquivo;
1705
- registro.simbolo = rota.simbolo;
1706
- }
1707
- else {
1708
- const resolucaoArquivo = escolherArquivoPorVinculo(todosArquivosConhecidos, superficieDeclarada);
1709
- registro.status = resolucaoArquivo.status;
1710
- registro.confianca = resolucaoArquivo.confianca;
1711
- registro.arquivo = resolucaoArquivo.arquivo;
1712
- }
2849
+ if (resumo.lacunas.includes("rastreabilidade_fraca")) {
2850
+ diagnosticos.push({
2851
+ tipo: "seguranca_frouxa",
2852
+ modulo: resumo.modulo,
2853
+ task: resumo.task,
2854
+ mensagem: `Task "${resumo.task}" exige producao mais rastreavel, mas ainda nao declara impl nem vinculos.`,
2855
+ });
1713
2856
  }
1714
- else {
1715
- const resolucaoArquivo = escolherArquivoPorVinculo(todosArquivosConhecidos, itemVinculo.vinculo.valor);
1716
- registro.status = resolucaoArquivo.status;
1717
- registro.confianca = resolucaoArquivo.confianca;
1718
- registro.arquivo = resolucaoArquivo.arquivo;
1719
- }
1720
- if (registro.status === "nao_encontrado" && itemVinculo.donoTipo === "superficie") {
1721
- const superficie = superficiesPorChave.get(itemVinculo.dono);
1722
- const ancora = superficie
1723
- ? encontrarAncoraSuperficie(ir, superficie, todosSimbolos, mapaImpl, todosArquivosConhecidos)
1724
- : undefined;
1725
- if (ancora) {
1726
- registro.status = "parcial";
1727
- registro.confianca = ancora.confianca === "alta" ? "media" : ancora.confianca;
1728
- registro.arquivo = registro.arquivo ?? ancora.arquivo;
1729
- registro.simbolo = registro.simbolo ?? ancora.simbolo;
1730
- }
2857
+ if (resumo.lacunas.includes("auth_ausente")) {
2858
+ diagnosticos.push({
2859
+ tipo: "seguranca_frouxa",
2860
+ modulo: resumo.modulo,
2861
+ task: resumo.task,
2862
+ mensagem: `Task "${resumo.task}" chega em superficie publica sem auth explicita em task, route ou superficie associada.`,
2863
+ });
1731
2864
  }
1732
- if (registro.status === "nao_encontrado") {
1733
- vinculosQuebrados.push(registro);
2865
+ if (resumo.lacunas.includes("authz_frouxa")) {
1734
2866
  diagnosticos.push({
1735
- tipo: "vinculo_quebrado",
1736
- modulo: ir.nome,
1737
- mensagem: `Vinculo ${registro.tipo}="${registro.valor}" de ${registro.donoTipo} "${registro.dono}" nao foi resolvido no codigo vivo.`,
1738
- ...(itemVinculo.donoTipo === "task" ? { task: itemVinculo.dono } : itemVinculo.donoTipo === "route" ? { route: itemVinculo.dono } : {}),
2867
+ tipo: "seguranca_frouxa",
2868
+ modulo: resumo.modulo,
2869
+ task: resumo.task,
2870
+ mensagem: `Task "${resumo.task}" opera com risco ou exposicao, mas ainda nao explicita authz suficiente.`,
1739
2871
  });
1740
2872
  }
1741
- else {
1742
- vinculosValidos.push(registro);
2873
+ if (resumo.lacunas.includes("dados_nao_classificados")) {
2874
+ diagnosticos.push({
2875
+ tipo: "seguranca_frouxa",
2876
+ modulo: resumo.modulo,
2877
+ task: resumo.task,
2878
+ mensagem: `Task "${resumo.task}" ainda nao classifica dados de entrada/saida de forma semantica.`,
2879
+ });
1743
2880
  }
1744
- if (itemVinculo.donoTipo === "task") {
1745
- const chaveTask = `${ir.nome}:${itemVinculo.dono}`;
1746
- const resumo = resumoVinculosPorTask.get(chaveTask) ?? {
1747
- validos: 0,
1748
- quebrados: 0,
1749
- arquivos: new Set(),
1750
- };
1751
- if (registro.status === "nao_encontrado") {
1752
- resumo.quebrados += 1;
1753
- }
1754
- else {
1755
- resumo.validos += 1;
1756
- }
1757
- if (registro.arquivo) {
1758
- resumo.arquivos.add(registro.arquivo);
1759
- }
1760
- resumoVinculosPorTask.set(chaveTask, resumo);
1761
- }
1762
- }
1763
- }
1764
- for (const resumo of tasksResumo) {
1765
- const chaveTask = `${resumo.modulo}:${resumo.task}`;
1766
- const task = taskPorChave.get(chaveTask);
1767
- const guardrails = guardrailsPorTask.get(chaveTask) ?? {
1768
- publica: false,
1769
- sensivel: false,
1770
- auth: false,
1771
- authz: false,
1772
- dados: false,
1773
- audit: false,
1774
- segredos: false,
1775
- forbidden: false,
1776
- dadosSensiveis: false,
1777
- efeitoPrivilegiado: false,
1778
- exigeSegredos: false,
2881
+ if (resumo.lacunas.includes("audit_ausente")) {
2882
+ diagnosticos.push({
2883
+ tipo: "seguranca_frouxa",
2884
+ modulo: resumo.modulo,
2885
+ task: resumo.task,
2886
+ mensagem: `Task "${resumo.task}" ainda nao declara audit explicita para operacao sensivel ou publica.`,
2887
+ });
2888
+ }
2889
+ if (resumo.lacunas.includes("segredo_sem_governanca")) {
2890
+ diagnosticos.push({
2891
+ tipo: "seguranca_frouxa",
2892
+ modulo: resumo.modulo,
2893
+ task: resumo.task,
2894
+ mensagem: `Task "${resumo.task}" toca segredo ou credencial sem bloco segredos governando origem, escopo e rotacao.`,
2895
+ });
2896
+ }
2897
+ if (resumo.lacunas.includes("proibicoes_ausentes")) {
2898
+ diagnosticos.push({
2899
+ tipo: "seguranca_frouxa",
2900
+ modulo: resumo.modulo,
2901
+ task: resumo.task,
2902
+ mensagem: `Task "${resumo.task}" opera com efeito privilegiado ou dado sensivel sem forbidden explicito para conter abuso e vazamento.`,
2903
+ });
2904
+ }
2905
+ }
2906
+ const consumersFiltrados = filtrarConsumerSurfacesPorEscopo([...indexTs.consumerSurfaces, ...indexDart.consumerSurfaces].sort((a, b) => a.rota.localeCompare(b.rota, "pt-BR")
2907
+ || a.tipoArquivo.localeCompare(b.tipoArquivo, "pt-BR")
2908
+ || a.arquivo.localeCompare(b.arquivo, "pt-BR")), [...new Map([...indexTs.simbolos, ...indexDart.simbolos]
2909
+ .filter((simbolo) => simboloEhBridgeConsumer(simbolo.caminho, simbolo.arquivo))
2910
+ .map((simbolo) => [
2911
+ `${simbolo.caminho}:${simbolo.arquivo}:${simbolo.simbolo}`,
2912
+ {
2913
+ caminho: simbolo.caminho,
2914
+ arquivo: simbolo.arquivo,
2915
+ simbolo: simbolo.simbolo,
2916
+ },
2917
+ ])).values()].sort((a, b) => a.caminho.localeCompare(b.caminho, "pt-BR")
2918
+ || a.arquivo.localeCompare(b.arquivo, "pt-BR")), contexto, configuracaoEscopo);
2919
+ const consumerSurfaces = consumersFiltrados.consumerSurfaces;
2920
+ const consumerBridges = consumersFiltrados.consumerBridges;
2921
+ const appRoutes = [...new Set(consumerSurfaces.map((surface) => surface.rota))]
2922
+ .sort((a, b) => a.localeCompare(b, "pt-BR"));
2923
+ const consumerFramework = inferirConsumerFrameworkPrincipal(contexto.fontesLegado, consumerSurfaces, consumerBridges);
2924
+ const persistenciaReal = await analisarPersistenciaReal(contexto, mapaRecursos, detalhesPersistencia, opcoesResolvidas);
2925
+ for (const item of persistenciaReal) {
2926
+ if (item.status === "divergente") {
2927
+ diagnosticos.push({
2928
+ tipo: "recurso_divergente",
2929
+ modulo: item.modulo,
2930
+ task: item.task,
2931
+ mensagem: `Persistencia real para "${item.alvo}" ainda nao foi materializada no codigo vivo.`,
2932
+ });
2933
+ }
2934
+ else if (item.compatibilidade === "invalido") {
2935
+ diagnosticos.push({
2936
+ tipo: "recurso_divergente",
2937
+ modulo: item.modulo,
2938
+ task: item.task,
2939
+ mensagem: `Persistencia real para "${item.alvo}" conflita com a compatibilidade declarada do engine ${item.engine}.`,
2940
+ });
2941
+ }
2942
+ }
2943
+ const payloadBase = {
2944
+ comando: "drift",
2945
+ sucesso: implsQuebrados.length === 0
2946
+ && rotasDivergentes.length === 0
2947
+ && recursosDivergentes.length === 0
2948
+ && vinculosQuebrados.length === 0
2949
+ && persistenciaReal.every((item) => item.status !== "divergente" && item.compatibilidade !== "invalido"),
2950
+ escopo_aplicado: configuracaoEscopo,
2951
+ consumerFramework,
2952
+ appRoutes,
2953
+ consumerSurfaces,
2954
+ consumerBridges,
2955
+ modulos: contexto.modulosSelecionados.map((item) => ({
2956
+ caminho: item.caminho,
2957
+ modulo: item.resultado.ir?.nome ?? item.resultado.modulo?.nome ?? null,
2958
+ tasks: item.resultado.ir?.tasks.length ?? 0,
2959
+ routes: item.resultado.ir?.routes.length ?? 0,
2960
+ })),
2961
+ tasks: tasksResumo,
2962
+ impls_validos: implsValidos,
2963
+ impls_quebrados: implsQuebrados,
2964
+ vinculos_validos: vinculosValidos,
2965
+ vinculos_quebrados: vinculosQuebrados,
2966
+ rotas_divergentes: rotasDivergentes,
2967
+ recursos_validos: recursosValidos,
2968
+ recursos_divergentes: recursosDivergentes,
2969
+ persistencia_real: persistenciaReal,
2970
+ diagnosticos,
2971
+ resumo_operacional: {
2972
+ scoreMedio: 0,
2973
+ confiancaGeral: "baixa",
2974
+ riscosPrincipais: [],
2975
+ oQueTocar: [],
2976
+ oQueValidar: [],
2977
+ oQueEstaFrouxo: [],
2978
+ oQueFoiInferido: [],
2979
+ },
1779
2980
  };
1780
- const resumoVinculos = resumoVinculosPorTask.get(chaveTask) ?? {
1781
- validos: 0,
1782
- quebrados: 0,
1783
- arquivos: new Set(),
2981
+ const resumoOperacional = resumirOperacional(payloadBase);
2982
+ return {
2983
+ ...payloadBase,
2984
+ resumo_operacional: resumoOperacional,
1784
2985
  };
1785
- if (!task) {
1786
- continue;
2986
+ }
2987
+ finally {
2988
+ diretoriosIgnoradosAtivos = diretoriosIgnoradosAnteriores;
2989
+ }
2990
+ }
2991
+ const EXTENSOES_BUSCA_IMPACTO = [
2992
+ ".sema",
2993
+ ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs",
2994
+ ".py", ".dart", ".cs", ".java", ".go", ".rs", ".cpp", ".cc", ".cxx", ".hpp", ".h",
2995
+ ".sql", ".psql", ".ddl", ".prisma", ".json",
2996
+ ];
2997
+ function construirVariantesSemanticas(valor) {
2998
+ const bruto = valor.trim();
2999
+ const partes = paraIdentificadorModulo(valor).split("_").filter(Boolean);
3000
+ if (!bruto && partes.length === 0) {
3001
+ return [];
3002
+ }
3003
+ const camel = partes.length > 0
3004
+ ? `${partes[0]}${partes.slice(1).map((item) => item[0]?.toUpperCase() + item.slice(1)).join("")}`
3005
+ : bruto;
3006
+ const pascal = partes.length > 0
3007
+ ? partes.map((item) => item[0]?.toUpperCase() + item.slice(1)).join("")
3008
+ : bruto;
3009
+ return [...new Set([
3010
+ bruto,
3011
+ partes.join("_"),
3012
+ partes.join("-"),
3013
+ partes.join("."),
3014
+ camel,
3015
+ pascal,
3016
+ ].filter(Boolean))];
3017
+ }
3018
+ function classificarArquivoImpacto(arquivo) {
3019
+ const normalizado = normalizarFragmentoArquivo(arquivo);
3020
+ if (normalizado.endsWith(".sema")) {
3021
+ return "contrato";
3022
+ }
3023
+ if (/\.(sql|psql|ddl|prisma)$/i.test(normalizado) || /(?:^|\/)(?:db|database|migrations?|schemas?)\//i.test(normalizado)) {
3024
+ return "persistencia";
3025
+ }
3026
+ if (/(?:^|\/)(?:repositorio|repositorios|repository|repositories|repo|dao|store)\//i.test(normalizado) || /(repository|repositorio|dao|store)/i.test(path.basename(normalizado))) {
3027
+ return "repositorio";
3028
+ }
3029
+ if (/(?:^|\/)(?:routes?|controllers?|api)\//i.test(normalizado) || /(controller|route)/i.test(path.basename(normalizado))) {
3030
+ return "rota";
3031
+ }
3032
+ if (/(?:^|\/)(?:workers?|jobs?|queues?|cron)\//i.test(normalizado) || /(worker|job|queue|cron)/i.test(path.basename(normalizado))) {
3033
+ return "worker";
3034
+ }
3035
+ if (/(?:^|\/)(?:pages|screens|components|views|app)\//i.test(normalizado)) {
3036
+ return "ui";
3037
+ }
3038
+ if (/(?:^|\/)(?:tests?|specs?|__tests__)\//i.test(normalizado) || /\.(spec|test)\./i.test(normalizado)) {
3039
+ return "teste";
3040
+ }
3041
+ return "codigo";
3042
+ }
3043
+ function prioridadeArquivoImpacto(tipo) {
3044
+ switch (tipo) {
3045
+ case "contrato":
3046
+ case "persistencia":
3047
+ case "repositorio":
3048
+ case "rota":
3049
+ return "alta";
3050
+ case "worker":
3051
+ case "codigo":
3052
+ return "media";
3053
+ default:
3054
+ return "baixa";
3055
+ }
3056
+ }
3057
+ function textoIrCombinaTermos(texto, termos) {
3058
+ return textoCombinaEscopo(texto, termos);
3059
+ }
3060
+ function registrarArquivoImpactado(mapa, arquivo, linhas, motivos) {
3061
+ const tipo = classificarArquivoImpacto(arquivo);
3062
+ const atual = mapa.get(arquivo);
3063
+ if (atual) {
3064
+ atual.linhas = [...new Set([...atual.linhas, ...linhas])].sort((a, b) => a - b);
3065
+ atual.motivos = [...new Set([...atual.motivos, ...motivos])];
3066
+ if (prioridadeArquivoImpacto(tipo) === "alta") {
3067
+ atual.prioridade = "alta";
1787
3068
  }
1788
- resumo.confiancaVinculo = calcularConfiancaTask(task, resumo.implsValidos, resumo.implsQuebrados, resumoVinculos.validos, resumoVinculos.quebrados);
1789
- resumo.riscoOperacional = calcularRiscoOperacional(task);
1790
- resumo.lacunas = resumirLacunasTask(task, resumo.semImplementacao, resumo.implsQuebrados, resumoVinculos.quebrados, guardrails);
1791
- resumo.scoreSemantico = calcularScoreTask(task, resumo.implsValidos, resumo.implsQuebrados, resumoVinculos.validos, resumoVinculos.quebrados, resumo.semImplementacao);
1792
- resumo.arquivosProvaveisEditar = [...new Set([
1793
- ...resumo.arquivosReferenciados,
1794
- ...resumo.candidatosImpl.map((candidato) => candidato.arquivo),
1795
- ...resumoVinculos.arquivos,
1796
- ])].sort((a, b) => a.localeCompare(b, "pt-BR"));
1797
- resumo.checksSugeridos = [...new Set([
1798
- ...task.resumoAgente.checks,
1799
- resumo.riscoOperacional !== "baixo" ? "revisar efeitos operacionais" : "",
1800
- resumo.lacunas.includes("vinculo_quebrado") ? "corrigir vinculos rastreaveis" : "",
1801
- resumo.lacunas.some((lacuna) => ["superficie_publica_sem_execucao", "execucao_critica_sem_bloco", "rastreabilidade_fraca"].includes(lacuna))
1802
- ? "endurecer execucao e rastreabilidade para producao"
1803
- : "",
1804
- resumo.lacunas.some((lacuna) => ["auth_ausente", "authz_frouxa", "dados_nao_classificados", "audit_ausente", "segredo_sem_governanca", "proibicoes_ausentes"].includes(lacuna))
1805
- ? "explicitar contratos de seguranca semantica"
1806
- : "",
1807
- ].filter(Boolean))];
1808
- if (resumo.lacunas.includes("superficie_publica_sem_execucao")) {
1809
- diagnosticos.push({
1810
- tipo: "seguranca_frouxa",
1811
- modulo: resumo.modulo,
1812
- task: resumo.task,
1813
- mensagem: `Task "${resumo.task}" alimenta superficie publica, mas ainda depende de execucao implicita.`,
1814
- });
3069
+ else if (prioridadeArquivoImpacto(tipo) === "media" && atual.prioridade === "baixa") {
3070
+ atual.prioridade = "media";
1815
3071
  }
1816
- if (resumo.lacunas.includes("execucao_critica_sem_bloco")) {
1817
- diagnosticos.push({
1818
- tipo: "seguranca_frouxa",
1819
- modulo: resumo.modulo,
1820
- task: resumo.task,
1821
- mensagem: `Task "${resumo.task}" opera com risco alto, mas ainda nao declarou execucao explicita.`,
1822
- });
1823
- }
1824
- if (resumo.lacunas.includes("rastreabilidade_fraca")) {
1825
- diagnosticos.push({
1826
- tipo: "seguranca_frouxa",
1827
- modulo: resumo.modulo,
1828
- task: resumo.task,
1829
- mensagem: `Task "${resumo.task}" exige producao mais rastreavel, mas ainda nao declara impl nem vinculos.`,
1830
- });
3072
+ return;
3073
+ }
3074
+ mapa.set(arquivo, {
3075
+ arquivo,
3076
+ tipo,
3077
+ prioridade: prioridadeArquivoImpacto(tipo),
3078
+ linhas: [...new Set(linhas)].sort((a, b) => a - b),
3079
+ motivos: [...new Set(motivos)],
3080
+ });
3081
+ }
3082
+ async function listarArquivosImpacto(contexto, opcoes) {
3083
+ const opcoesResolvidas = resolverOpcoesDrift(opcoes);
3084
+ const arquivos = new Set(filtrarCaminhosEscopoReal(contexto.arquivosProjeto, contexto, opcoesResolvidas));
3085
+ for (const diretorio of resolverDiretoriosCodigoEscopoReal(contexto, opcoesResolvidas)) {
3086
+ for (const arquivo of await listarArquivosRecursivos(diretorio, EXTENSOES_BUSCA_IMPACTO)) {
3087
+ arquivos.add(arquivo);
1831
3088
  }
1832
- if (resumo.lacunas.includes("auth_ausente")) {
1833
- diagnosticos.push({
1834
- tipo: "seguranca_frouxa",
1835
- modulo: resumo.modulo,
1836
- task: resumo.task,
1837
- mensagem: `Task "${resumo.task}" chega em superficie publica sem auth explicita em task, route ou superficie associada.`,
1838
- });
3089
+ }
3090
+ return [...arquivos].sort((a, b) => a.localeCompare(b, "pt-BR"));
3091
+ }
3092
+ function extrairLinhasComVariantes(codigo, variantes) {
3093
+ const linhas = [];
3094
+ const texto = codigo.split(/\r?\n/);
3095
+ for (let indice = 0; indice < texto.length; indice += 1) {
3096
+ if (variantes.some((variante) => variante && texto[indice].includes(variante))) {
3097
+ linhas.push(indice + 1);
1839
3098
  }
1840
- if (resumo.lacunas.includes("authz_frouxa")) {
1841
- diagnosticos.push({
1842
- tipo: "seguranca_frouxa",
1843
- modulo: resumo.modulo,
1844
- task: resumo.task,
1845
- mensagem: `Task "${resumo.task}" opera com risco ou exposicao, mas ainda nao explicita authz suficiente.`,
1846
- });
3099
+ }
3100
+ return linhas;
3101
+ }
3102
+ function serializarTaskParaImpacto(task) {
3103
+ return JSON.stringify({
3104
+ nome: task.nome,
3105
+ input: task.input.map((campo) => campo.nome),
3106
+ output: task.output.map((campo) => campo.nome),
3107
+ effects: task.effects,
3108
+ guarantees: task.guarantees,
3109
+ errors: task.errors,
3110
+ resumo: task.resumoAgente,
3111
+ });
3112
+ }
3113
+ function serializarRouteParaImpacto(route) {
3114
+ return JSON.stringify({
3115
+ nome: route.nome,
3116
+ caminho: route.caminho,
3117
+ metodo: route.metodo,
3118
+ task: route.task,
3119
+ input: route.inputPublico.map((campo) => campo.nome),
3120
+ output: route.outputPublico.map((campo) => campo.nome),
3121
+ });
3122
+ }
3123
+ function serializarSuperficieParaImpacto(superficie) {
3124
+ return JSON.stringify({
3125
+ tipo: superficie.tipo,
3126
+ nome: superficie.nome,
3127
+ task: superficie.task,
3128
+ input: superficie.input.map((campo) => campo.nome),
3129
+ output: superficie.output.map((campo) => campo.nome),
3130
+ });
3131
+ }
3132
+ function ordenarArquivosImpacto(arquivos) {
3133
+ const ordemPrioridade = { alta: 0, media: 1, baixa: 2 };
3134
+ return [...arquivos].sort((a, b) => ordemPrioridade[a.prioridade] - ordemPrioridade[b.prioridade]
3135
+ || a.tipo.localeCompare(b.tipo, "pt-BR")
3136
+ || a.arquivo.localeCompare(b.arquivo, "pt-BR"));
3137
+ }
3138
+ export async function gerarMapaImpactoSemantico(contexto, alvoSemantico, mudancaProposta, opcoes) {
3139
+ const opcoesResolvidas = resolverOpcoesDrift(opcoes);
3140
+ const diretoriosIgnoradosAnteriores = diretoriosIgnoradosAtivos;
3141
+ diretoriosIgnoradosAtivos = resolverDiretoriosIgnoradosAtivos(opcoesResolvidas);
3142
+ try {
3143
+ const drift = await analisarDriftLegado(contexto, opcoesResolvidas);
3144
+ const variantes = construirVariantesSemanticas(alvoSemantico);
3145
+ const termos = [...new Set([...quebrarTermosEscopo(alvoSemantico), ...drift.escopo_aplicado.termosEscopo])];
3146
+ const arquivosImpactados = new Map();
3147
+ const arquivosBusca = await listarArquivosImpacto(contexto, opcoesResolvidas);
3148
+ for (const arquivo of arquivosBusca) {
3149
+ const codigo = await readFile(arquivo, "utf8");
3150
+ const linhas = extrairLinhasComVariantes(codigo, variantes);
3151
+ if (linhas.length > 0) {
3152
+ registrarArquivoImpactado(arquivosImpactados, arquivo, linhas, ["token_semantico_encontrado"]);
3153
+ }
1847
3154
  }
1848
- if (resumo.lacunas.includes("dados_nao_classificados")) {
1849
- diagnosticos.push({
1850
- tipo: "seguranca_frouxa",
1851
- modulo: resumo.modulo,
1852
- task: resumo.task,
1853
- mensagem: `Task "${resumo.task}" ainda nao classifica dados de entrada/saida de forma semantica.`,
1854
- });
3155
+ const tasksAfetadas = new Set();
3156
+ const routesAfetadas = new Set();
3157
+ const superficiesAfetadas = new Set();
3158
+ const persistenciaAfetada = new Set();
3159
+ for (const item of contexto.modulosSelecionados) {
3160
+ const ir = item.resultado.ir;
3161
+ if (!ir) {
3162
+ continue;
3163
+ }
3164
+ for (const task of ir.tasks) {
3165
+ if (textoIrCombinaTermos(serializarTaskParaImpacto(task), termos)) {
3166
+ tasksAfetadas.add(`${ir.nome}.${task.nome}`);
3167
+ }
3168
+ }
3169
+ for (const route of ir.routes) {
3170
+ if (textoIrCombinaTermos(serializarRouteParaImpacto(route), termos)) {
3171
+ routesAfetadas.add(`${ir.nome}.${route.nome}`);
3172
+ }
3173
+ }
3174
+ for (const superficie of ir.superficies) {
3175
+ if (textoIrCombinaTermos(serializarSuperficieParaImpacto(superficie), termos)) {
3176
+ superficiesAfetadas.add(`${ir.nome}.${superficie.tipo}.${superficie.nome}`);
3177
+ }
3178
+ }
1855
3179
  }
1856
- if (resumo.lacunas.includes("audit_ausente")) {
1857
- diagnosticos.push({
1858
- tipo: "seguranca_frouxa",
1859
- modulo: resumo.modulo,
1860
- task: resumo.task,
1861
- mensagem: `Task "${resumo.task}" ainda nao declara audit explicita para operacao sensivel ou publica.`,
1862
- });
3180
+ for (const task of drift.tasks.filter((item) => tasksAfetadas.has(`${item.modulo}.${item.task}`))) {
3181
+ for (const arquivo of task.arquivosProvaveisEditar) {
3182
+ registrarArquivoImpactado(arquivosImpactados, arquivo, [], ["arquivo_relacionado_por_drift"]);
3183
+ }
1863
3184
  }
1864
- if (resumo.lacunas.includes("segredo_sem_governanca")) {
1865
- diagnosticos.push({
1866
- tipo: "seguranca_frouxa",
1867
- modulo: resumo.modulo,
1868
- task: resumo.task,
1869
- mensagem: `Task "${resumo.task}" toca segredo ou credencial sem bloco segredos governando origem, escopo e rotacao.`,
1870
- });
3185
+ for (const item of drift.persistencia_real) {
3186
+ if (textoIrCombinaTermos(`${item.alvo} ${item.task} ${item.colunas.join(" ")}`, termos)) {
3187
+ persistenciaAfetada.add(`${item.task}:${item.alvo}`);
3188
+ for (const arquivo of [...item.arquivos, ...item.repositorios]) {
3189
+ registrarArquivoImpactado(arquivosImpactados, arquivo, [], ["persistencia_relacionada"]);
3190
+ }
3191
+ }
1871
3192
  }
1872
- if (resumo.lacunas.includes("proibicoes_ausentes")) {
1873
- diagnosticos.push({
1874
- tipo: "seguranca_frouxa",
1875
- modulo: resumo.modulo,
1876
- task: resumo.task,
1877
- mensagem: `Task "${resumo.task}" opera com efeito privilegiado ou dado sensivel sem forbidden explicito para conter abuso e vazamento.`,
1878
- });
3193
+ const contratosAfetados = ordenarArquivosImpacto([...arquivosImpactados.values()].filter((arquivo) => arquivo.tipo === "contrato")).map((arquivo) => arquivo.arquivo);
3194
+ return {
3195
+ comando: "impacto",
3196
+ sucesso: arquivosImpactados.size > 0 || tasksAfetadas.size > 0 || persistenciaAfetada.size > 0,
3197
+ escopo: drift.escopo_aplicado.escopo,
3198
+ alvoSemantico,
3199
+ mudancaProposta,
3200
+ contratosAfetados,
3201
+ tasksAfetadas: [...tasksAfetadas].sort((a, b) => a.localeCompare(b, "pt-BR")),
3202
+ routesAfetadas: [...routesAfetadas].sort((a, b) => a.localeCompare(b, "pt-BR")),
3203
+ superficiesAfetadas: [...superficiesAfetadas].sort((a, b) => a.localeCompare(b, "pt-BR")),
3204
+ persistenciaAfetada: [...persistenciaAfetada].sort((a, b) => a.localeCompare(b, "pt-BR")),
3205
+ arquivos: ordenarArquivosImpacto([...arquivosImpactados.values()]),
3206
+ ordemOperacional: [
3207
+ "Atualizar contrato .sema e revisar garantias publicas primeiro.",
3208
+ "Ajustar persistencia e repositorios concretos antes de materializacao externa.",
3209
+ "Revisar rotas, workers e bridges depois que o contrato e o storage estiverem coerentes.",
3210
+ "Fechar com UI/consumidores e testes alinhados ao payload final.",
3211
+ ],
3212
+ validacoes: [
3213
+ "Rodar sema validar no contrato alterado.",
3214
+ "Rodar sema drift com o mesmo escopo apos a mudanca.",
3215
+ "Revalidar testes de payload, persistencia e superficies publicas.",
3216
+ ],
3217
+ };
3218
+ }
3219
+ finally {
3220
+ diretoriosIgnoradosAtivos = diretoriosIgnoradosAnteriores;
3221
+ }
3222
+ }
3223
+ export async function assistirRenomeacaoSemantica(contexto, nomeAtual, nomeNovo, opcoes) {
3224
+ const impacto = await gerarMapaImpactoSemantico(contexto, nomeAtual, `renomear ${nomeAtual} para ${nomeNovo}`, opcoes);
3225
+ const variantesAntigas = construirVariantesSemanticas(nomeAtual);
3226
+ const variantesNovas = construirVariantesSemanticas(nomeNovo);
3227
+ const mapaSubstituicao = new Map();
3228
+ variantesAntigas.forEach((antiga, indice) => {
3229
+ mapaSubstituicao.set(antiga, variantesNovas[indice] ?? nomeNovo);
3230
+ });
3231
+ const sugestoes = [];
3232
+ for (const arquivo of impacto.arquivos) {
3233
+ const codigo = await readFile(arquivo.arquivo, "utf8");
3234
+ const linhas = codigo.split(/\r?\n/);
3235
+ for (let indice = 0; indice < linhas.length; indice += 1) {
3236
+ const linha = linhas[indice];
3237
+ for (const antiga of variantesAntigas) {
3238
+ if (!antiga || !linha.includes(antiga)) {
3239
+ continue;
3240
+ }
3241
+ sugestoes.push({
3242
+ arquivo: arquivo.arquivo,
3243
+ linha: indice + 1,
3244
+ atual: antiga,
3245
+ sugerido: mapaSubstituicao.get(antiga) ?? nomeNovo,
3246
+ contexto: linha.trim().slice(0, 180),
3247
+ });
3248
+ }
1879
3249
  }
1880
3250
  }
1881
- const consumerSurfaces = [...indexTs.consumerSurfaces, ...indexDart.consumerSurfaces].sort((a, b) => a.rota.localeCompare(b.rota, "pt-BR")
1882
- || a.tipoArquivo.localeCompare(b.tipoArquivo, "pt-BR")
1883
- || a.arquivo.localeCompare(b.arquivo, "pt-BR"));
1884
- const consumerBridges = [...new Map([...indexTs.simbolos, ...indexDart.simbolos]
1885
- .filter((simbolo) => simboloEhBridgeConsumer(simbolo.caminho, simbolo.arquivo))
1886
- .map((simbolo) => [
1887
- `${simbolo.caminho}:${simbolo.arquivo}:${simbolo.simbolo}`,
1888
- {
1889
- caminho: simbolo.caminho,
1890
- arquivo: simbolo.arquivo,
1891
- simbolo: simbolo.simbolo,
1892
- },
1893
- ])).values()].sort((a, b) => a.caminho.localeCompare(b.caminho, "pt-BR")
1894
- || a.arquivo.localeCompare(b.arquivo, "pt-BR"));
1895
- const appRoutes = [...new Set(consumerSurfaces.map((surface) => surface.rota))]
1896
- .sort((a, b) => a.localeCompare(b, "pt-BR"));
1897
- const consumerFramework = inferirConsumerFrameworkPrincipal(contexto.fontesLegado, consumerSurfaces, consumerBridges);
1898
- const payloadBase = {
1899
- comando: "drift",
1900
- sucesso: implsQuebrados.length === 0 && rotasDivergentes.length === 0 && recursosDivergentes.length === 0 && vinculosQuebrados.length === 0,
1901
- consumerFramework,
1902
- appRoutes,
1903
- consumerSurfaces,
1904
- consumerBridges,
1905
- modulos: contexto.modulosSelecionados.map((item) => ({
1906
- caminho: item.caminho,
1907
- modulo: item.resultado.ir?.nome ?? item.resultado.modulo?.nome ?? null,
1908
- tasks: item.resultado.ir?.tasks.length ?? 0,
1909
- routes: item.resultado.ir?.routes.length ?? 0,
1910
- })),
1911
- tasks: tasksResumo,
1912
- impls_validos: implsValidos,
1913
- impls_quebrados: implsQuebrados,
1914
- vinculos_validos: vinculosValidos,
1915
- vinculos_quebrados: vinculosQuebrados,
1916
- rotas_divergentes: rotasDivergentes,
1917
- recursos_validos: recursosValidos,
1918
- recursos_divergentes: recursosDivergentes,
1919
- diagnosticos,
1920
- resumo_operacional: {
1921
- scoreMedio: 0,
1922
- confiancaGeral: "baixa",
1923
- riscosPrincipais: [],
1924
- oQueTocar: [],
1925
- oQueValidar: [],
1926
- oQueEstaFrouxo: [],
1927
- oQueFoiInferido: [],
1928
- },
1929
- };
1930
- const resumoOperacional = resumirOperacional(payloadBase);
1931
3251
  return {
1932
- ...payloadBase,
1933
- resumo_operacional: resumoOperacional,
3252
+ comando: "renomear-semantico",
3253
+ sucesso: sugestoes.length > 0,
3254
+ escopo: impacto.escopo,
3255
+ de: nomeAtual,
3256
+ para: nomeNovo,
3257
+ arquivos: impacto.arquivos,
3258
+ sugestoes,
3259
+ ordemOperacional: [
3260
+ "Renomear primeiro no contrato .sema e nos campos publicos derivados.",
3261
+ "Ajustar repositorios, payloads e bridges que materializam o nome antigo.",
3262
+ "Rodar sema drift e revisar sugestoes restantes antes de fechar a troca.",
3263
+ ],
3264
+ validacoes: [
3265
+ "Rodar sema validar no contrato renomeado.",
3266
+ "Rodar sema drift para confirmar que payload e superficie nao ficaram misturados.",
3267
+ "Reexecutar testes e checar snapshots ou fixtures afetados.",
3268
+ ],
1934
3269
  };
1935
3270
  }
1936
3271
  //# sourceMappingURL=drift.js.map