@semacode/cli 1.5.4 → 1.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/drift.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { readdir, readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import ts from "typescript";
4
+ import { coletarSuperficiesAngularStandaloneConsumer } from "./angular-consumer-standalone.js";
4
5
  import { extrairSimbolosCpp } from "./cpp-symbols.js";
5
6
  import { extrairRotasDotnet, extrairSimbolosDotnet } from "./dotnet-http.js";
6
7
  import { extrairRotasGo, extrairSimbolosGo } from "./go-http.js";
@@ -677,6 +678,8 @@ function extrairRecursosArquivoLocal(arquivo, codigo) {
677
678
  const contextoArquivoLocal = /\b(?:json|jsonl|ndjson)\b/i.test(codigo)
678
679
  || /\b(?:read_text|write_text|readFile(?:Sync)?|writeFile(?:Sync)?|open)\b/i.test(codigo)
679
680
  || /\.(?:json|jsonl|ndjson|db|sqlite|sqlite3)\b/i.test(codigo)
681
+ || /@capacitor\/preferences|Preferences\.(?:get|set|remove)\s*\(/i.test(codigo)
682
+ || /\b(?:localStorage|sessionStorage)\.(?:getItem|setItem|removeItem)\s*\(/i.test(codigo)
680
683
  || /(?:repository|repositories|repositorio|repo|store|storage|persist|cache)/i.test(normalizarFragmentoArquivo(arquivo));
681
684
  if (!contextoArquivoLocal) {
682
685
  return [];
@@ -693,6 +696,12 @@ function extrairRecursosArquivoLocal(arquivo, codigo) {
693
696
  if (nomeStore && /(?:repository|repositories|repositorio|repo|store|storage|persist|cache)/i.test(nomeArquivo)) {
694
697
  registrarRecursoDrift(recursos, "arquivo", "arquivo_local", nomeStore, arquivo);
695
698
  }
699
+ for (const match of codigo.matchAll(/Preferences\.(?:get|set|remove)\s*\(\s*\{[\s\S]{0,160}?key\s*:\s*["'`]([^"'`]+)["'`]/gi)) {
700
+ registrarRecursoDrift(recursos, "arquivo", "arquivo_local", match[1], arquivo);
701
+ }
702
+ for (const match of codigo.matchAll(/\b(?:localStorage|sessionStorage)\.(?:getItem|setItem|removeItem)\s*\(\s*["'`]([^"'`]+)["'`]/gi)) {
703
+ registrarRecursoDrift(recursos, "arquivo", "arquivo_local", match[1], arquivo);
704
+ }
696
705
  return [...recursos.values()];
697
706
  }
698
707
  function extrairRecursosPersistenciaCodigoVivo(arquivo, codigo) {
@@ -1445,7 +1454,10 @@ function inferirConsumerFrameworkPrincipal(fontesLegado, consumerSurfaces, consu
1445
1454
  || /(?:^|\/)(?:src\/)?(?:app\/)?(?:router|routes)\.(?:ts|tsx|js|jsx)$/i.test(arquivo))) {
1446
1455
  return "react-vite-consumer";
1447
1456
  }
1448
- if (arquivos.some((arquivo) => /(?:^|\/)(?:src\/)?app\/.+\.component\.(?:ts|js)$/i.test(arquivo) || arquivoEhRotasAngularConsumer(arquivo))) {
1457
+ if (arquivos.some((arquivo) => /(?:^|\/)(?:src\/)?app\.component\.(?:ts|js)$/i.test(arquivo)
1458
+ || /(?:^|\/)(?:src\/)?app\/.+\.component\.(?:ts|js)$/i.test(arquivo)
1459
+ || /(?:^|\/)(?:src\/)?components\/.+\.component\.(?:ts|js)$/i.test(arquivo)
1460
+ || arquivoEhRotasAngularConsumer(arquivo))) {
1449
1461
  return "angular-consumer";
1450
1462
  }
1451
1463
  if (arquivos.some((arquivo) => /(?:^|\/)(?:lib\/)?(?:screens|pages)\/.+\.dart$/i.test(arquivo)
@@ -1501,6 +1513,7 @@ async function indexarTypeScript(diretorios) {
1501
1513
  .filter((arquivo) => arquivoEhRotasAngularConsumerRaiz(path.relative(diretorio, arquivo)))
1502
1514
  .map((arquivo) => path.resolve(arquivo)));
1503
1515
  const usarApenasRotasAngularRaiz = arquivosRotasAngularRaiz.size > 0;
1516
+ let encontrouSuperficieAngularPorRotas = false;
1504
1517
  for (const arquivo of arquivos) {
1505
1518
  const codigo = await readFile(arquivo, "utf8");
1506
1519
  const scriptKind = arquivo.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
@@ -1578,6 +1591,7 @@ async function indexarTypeScript(diretorios) {
1578
1591
  }
1579
1592
  }
1580
1593
  if (arquivoEhRotasAngularConsumer(relacao) && (!usarApenasRotasAngularRaiz || arquivosRotasAngularRaiz.has(path.resolve(arquivo)))) {
1594
+ encontrouSuperficieAngularPorRotas = true;
1581
1595
  for (const rotaAngular of await extrairRotasAngularConsumer(diretorio, relacao)) {
1582
1596
  const arquivoRotasAngular = path.join(diretorio, rotaAngular.arquivoRotas);
1583
1597
  consumerSurfaces.set(`${rotaAngular.rota}:${arquivoRotasAngular}:routes`, {
@@ -1673,6 +1687,23 @@ async function indexarTypeScript(diretorios) {
1673
1687
  }
1674
1688
  }
1675
1689
  }
1690
+ if (!encontrouSuperficieAngularPorRotas) {
1691
+ for (const superficie of await coletarSuperficiesAngularStandaloneConsumer(diretorio, arquivos)) {
1692
+ const arquivoSuperficie = path.join(diretorio, superficie.arquivo);
1693
+ consumerSurfaces.set(`${superficie.rota}:${arquivoSuperficie}:${superficie.tipoArquivo}`, {
1694
+ rota: superficie.rota,
1695
+ arquivo: arquivoSuperficie,
1696
+ tipoArquivo: superficie.tipoArquivo,
1697
+ });
1698
+ rotas.push({
1699
+ origem: "angular-consumer",
1700
+ metodo: "VIEW",
1701
+ caminho: superficie.rota,
1702
+ arquivo: arquivoSuperficie,
1703
+ simbolo: superficie.tipoArquivo,
1704
+ });
1705
+ }
1706
+ }
1676
1707
  }
1677
1708
  return {
1678
1709
  simbolos: [...simbolos.values()],
@@ -2190,12 +2221,18 @@ function extrairCamposArquivoLocalDetalhados(arquivo, codigo) {
2190
2221
  registrarCampo(recurso.nome, bloco[1] ?? "");
2191
2222
  }
2192
2223
  }
2224
+ for (const match of codigo.matchAll(/Preferences\.(?:get|set|remove)\s*\(\s*\{[\s\S]{0,160}?key\s*:\s*["'`]([^"'`]+)["'`]/gi)) {
2225
+ registrarColunaPersistenciaDrift(colunas, "arquivo", match[1], match[1], arquivo);
2226
+ }
2227
+ for (const match of codigo.matchAll(/\b(?:localStorage|sessionStorage)\.(?:getItem|setItem|removeItem)\s*\(\s*["'`]([^"'`]+)["'`]/gi)) {
2228
+ registrarColunaPersistenciaDrift(colunas, "arquivo", match[1], match[1], arquivo);
2229
+ }
2193
2230
  return [...colunas.values()];
2194
2231
  }
2195
2232
  function registrarRepositoriosPorRecursos(repositorios, arquivo, codigo, recursos) {
2196
2233
  const contextoRepositorio = /(?:repository|repositories|repositorio|repositorios|repo|dao|store|queries|persistence|persistencia)/i.test(arquivo)
2197
2234
  || /\b(?:Repository|Repositories|Dao|Store)\b/.test(codigo);
2198
- const contextoAcesso = /\b(?:select|insert|update|delete|aggregate|findOne|findMany|findUnique|findFirst|prisma\.|db\.collection|createClient|hset|hget|xadd|xread|json\.(?:load|loads|dump|dumps)|JSON\.(?:parse|stringify)|read_text|write_text|readFile(?:Sync)?|writeFile(?:Sync)?|open)\b/i.test(codigo)
2235
+ const contextoAcesso = /\b(?:select|insert|update|delete|aggregate|findOne|findMany|findUnique|findFirst|prisma\.|db\.collection|createClient|hset|hget|xadd|xread|json\.(?:load|loads|dump|dumps)|JSON\.(?:parse|stringify)|read_text|write_text|readFile(?:Sync)?|writeFile(?:Sync)?|open|Preferences\.(?:get|set|remove)|localStorage\.(?:getItem|setItem|removeItem)|sessionStorage\.(?:getItem|setItem|removeItem))\b/i.test(codigo)
2199
2236
  || /\.(?:json|jsonl|ndjson|db|sqlite|sqlite3)\b/i.test(codigo);
2200
2237
  if (!contextoRepositorio && !contextoAcesso) {
2201
2238
  return;
@@ -2255,7 +2292,7 @@ function arquivoCombinaDeclaradoDrift(arquivoReal, arquivoDeclarado) {
2255
2292
  const declarado = normalizarArquivoDeclaradoDrift(arquivoDeclarado);
2256
2293
  return real === declarado || real.endsWith(declarado) || declarado.endsWith(real);
2257
2294
  }
2258
- function coletarArquivosPreferidosPersistenciaTask(task) {
2295
+ function coletarArquivosPreferidosPersistenciaTask(task, mapaImpl) {
2259
2296
  const arquivos = new Set();
2260
2297
  for (const vinculo of task.vinculos) {
2261
2298
  if (vinculo.arquivo) {
@@ -2265,17 +2302,45 @@ function coletarArquivosPreferidosPersistenciaTask(task) {
2265
2302
  arquivos.add(vinculo.valor);
2266
2303
  }
2267
2304
  }
2305
+ if (mapaImpl) {
2306
+ for (const impl of task.implementacoesExternas) {
2307
+ const resolvido = mapaImpl.get(impl.caminho);
2308
+ if (resolvido?.arquivo) {
2309
+ arquivos.add(resolvido.arquivo);
2310
+ }
2311
+ }
2312
+ }
2268
2313
  return arquivos;
2269
2314
  }
2270
- function resolverPersistenciaLocalPorTask(mapaRecursos, task, ir, esperado) {
2315
+ function resolverPersistenciaLocalPorTask(mapaRecursos, task, ir, esperado, mapaImpl) {
2271
2316
  const todosRecursos = deduplicarRecursosResolvidos([...mapaRecursos.values()].flat());
2272
- const arquivosPreferidos = [...coletarArquivosPreferidosPersistenciaTask(task)];
2317
+ const arquivosPreferidos = [...coletarArquivosPreferidosPersistenciaTask(task, mapaImpl)];
2273
2318
  const candidatosPorArquivo = arquivosPreferidos.length > 0
2274
2319
  ? todosRecursos.filter((recurso) => recurso.origem === "arquivo"
2275
2320
  && arquivosPreferidos.some((arquivo) => arquivoCombinaDeclaradoDrift(recurso.arquivo, arquivo)))
2276
2321
  : [];
2322
+ const variantesAlvo = new Set(variantesNomeRecursoDrift(esperado.alvo));
2323
+ const normalizarBusca = (valor) => valor.toLowerCase().replace(/[^a-z0-9]+/g, "_");
2324
+ const pontuarCandidato = (recurso) => {
2325
+ let score = 0;
2326
+ if (recursoResolvidoCombinaEsperado(recurso, esperado)) {
2327
+ score += 4;
2328
+ }
2329
+ if (variantesNomeRecursoDrift(recurso.nome).some((variacao) => variantesAlvo.has(variacao))) {
2330
+ score += 2;
2331
+ }
2332
+ const nomeNormalizado = normalizarBusca(recurso.nome);
2333
+ const alvoNormalizado = normalizarBusca(esperado.alvo);
2334
+ if (nomeNormalizado.includes(alvoNormalizado) || alvoNormalizado.includes(nomeNormalizado)) {
2335
+ score += 1;
2336
+ }
2337
+ return score;
2338
+ };
2339
+ const ordenarCandidatos = (candidatos) => [...candidatos].sort((a, b) => pontuarCandidato(b) - pontuarCandidato(a)
2340
+ || a.nome.localeCompare(b.nome, "pt-BR")
2341
+ || a.arquivo.localeCompare(b.arquivo, "pt-BR"));
2277
2342
  if (candidatosPorArquivo.length > 0) {
2278
- return candidatosPorArquivo;
2343
+ return ordenarCandidatos(candidatosPorArquivo);
2279
2344
  }
2280
2345
  const termos = new Set([
2281
2346
  ...quebrarTermosEscopo(ir.nome),
@@ -2285,8 +2350,8 @@ function resolverPersistenciaLocalPorTask(mapaRecursos, task, ir, esperado) {
2285
2350
  if (termos.size === 0) {
2286
2351
  return [];
2287
2352
  }
2288
- return todosRecursos.filter((recurso) => recurso.origem === "arquivo"
2289
- && [...termos].some((termo) => variantesNomeRecursoDrift(recurso.nome).some((variacao) => variacao.includes(termo))));
2353
+ return ordenarCandidatos(todosRecursos.filter((recurso) => recurso.origem === "arquivo"
2354
+ && [...termos].some((termo) => variantesNomeRecursoDrift(recurso.nome).some((variacao) => variacao.includes(termo)))));
2290
2355
  }
2291
2356
  function detalhePersistenciaCombinaOrigem(origemDetalhe, recursoReal) {
2292
2357
  if (!recursoReal) {
@@ -2334,7 +2399,7 @@ function localizarCompatibilidadePersistencia(bancos, esperado, recursoReal) {
2334
2399
  tipo: recursoReal?.tipo ?? esperado.tiposAceitos[0] ?? "query",
2335
2400
  };
2336
2401
  }
2337
- export async function analisarPersistenciaReal(contexto, mapaRecursos, detalhesPersistencia, opcoes) {
2402
+ export async function analisarPersistenciaReal(contexto, mapaRecursos, detalhesPersistencia, opcoes, mapaImpl) {
2338
2403
  const opcoesResolvidas = resolverOpcoesDrift(opcoes);
2339
2404
  const diretoriosCodigoAtivos = resolverDiretoriosCodigoEscopoReal(contexto, opcoesResolvidas);
2340
2405
  const mapa = mapaRecursos ?? construirMapaRecursos((await indexarPersistenciaDeclarativa(diretoriosCodigoAtivos)).recursos);
@@ -2346,12 +2411,12 @@ export async function analisarPersistenciaReal(contexto, mapaRecursos, detalhesP
2346
2411
  continue;
2347
2412
  }
2348
2413
  for (const task of ir.tasks) {
2349
- for (const esperado of extrairRecursosEsperados(task, ir)) {
2414
+ for (const esperado of extrairRecursosEsperados(task, ir, mapa, mapaImpl)) {
2350
2415
  const correspondencias = esperado.nomes.flatMap((nome) => variantesNomeRecursoDrift(nome).flatMap((variante) => (mapa.get(variante) ?? []).filter((recurso) => recursoResolvidoCombinaEsperado(recurso, esperado))));
2351
2416
  let recursosReais = deduplicarRecursosResolvidos(correspondencias);
2352
- const arquivosPreferidos = [...coletarArquivosPreferidosPersistenciaTask(task)];
2417
+ const arquivosPreferidos = [...coletarArquivosPreferidosPersistenciaTask(task, mapaImpl)];
2353
2418
  if (recursosReais.length === 0) {
2354
- recursosReais = resolverPersistenciaLocalPorTask(mapa, task, ir, esperado);
2419
+ recursosReais = resolverPersistenciaLocalPorTask(mapa, task, ir, esperado, mapaImpl);
2355
2420
  }
2356
2421
  const compatibilidade = localizarCompatibilidadePersistencia(ir.databases, esperado, recursosReais[0]);
2357
2422
  let colunas = [...new Set(detalhes.colunas
@@ -2624,11 +2689,25 @@ function recursoPersistenciaCombinaAlvo(recurso, alvo) {
2624
2689
  }
2625
2690
  return nomesRecursoPersistencia(recurso).some((nome) => variantesNomeRecursoDrift(nome).some((variacao) => alvoVariantes.has(variacao)));
2626
2691
  }
2627
- function taskSugerePersistenciaSemBanco(task) {
2628
- return task.vinculos.some((vinculo) => /(?:repository|repositories|repositorio|repo|store|storage|persist|cache)/i.test(`${vinculo.valor} ${vinculo.arquivo ?? ""} ${vinculo.simbolo ?? ""}`))
2629
- || task.implementacoesExternas.some((impl) => /(?:repository|repositories|repositorio|repo|store|storage|persist|cache)/i.test(impl.caminho));
2692
+ function taskSugerePersistenciaSemBanco(task, mapaRecursos, mapaImpl) {
2693
+ if (task.vinculos.some((vinculo) => /(?:repository|repositories|repositorio|repo|store|storage|persist|cache)/i.test(`${vinculo.valor} ${vinculo.arquivo ?? ""} ${vinculo.simbolo ?? ""}`))) {
2694
+ return true;
2695
+ }
2696
+ if (task.implementacoesExternas.some((impl) => /(?:repository|repositories|repositorio|repo|store|storage|persist|cache)/i.test(impl.caminho))) {
2697
+ return true;
2698
+ }
2699
+ if (!mapaRecursos || !mapaImpl) {
2700
+ return false;
2701
+ }
2702
+ const arquivosPreferidos = [...coletarArquivosPreferidosPersistenciaTask(task, mapaImpl)];
2703
+ if (arquivosPreferidos.length === 0) {
2704
+ return false;
2705
+ }
2706
+ const recursos = deduplicarRecursosResolvidos([...mapaRecursos.values()].flat());
2707
+ return recursos.some((recurso) => recurso.origem === "arquivo"
2708
+ && arquivosPreferidos.some((arquivo) => arquivoCombinaDeclaradoDrift(recurso.arquivo, arquivo)));
2630
2709
  }
2631
- function extrairRecursosEsperados(task, ir) {
2710
+ function extrairRecursosEsperados(task, ir, mapaRecursos, mapaImpl) {
2632
2711
  const esperados = new Map();
2633
2712
  const registrar = (esperado) => {
2634
2713
  const chave = `${esperado.origem ?? "qualquer"}:${esperado.tiposAceitos.join(",")}:${esperado.nomes.join("|")}:${esperado.alvo}`;
@@ -2652,9 +2731,9 @@ function extrairRecursosEsperados(task, ir) {
2652
2731
  return [...esperados.values()];
2653
2732
  }
2654
2733
  if (ir.databases.length === 0) {
2655
- const sugerePersistenciaLocal = taskSugerePersistenciaSemBanco(task);
2734
+ const sugerePersistenciaLocal = taskSugerePersistenciaSemBanco(task, mapaRecursos, mapaImpl);
2656
2735
  for (const efeito of efeitosPersistencia) {
2657
- if (efeito.categoria !== "persistencia" || !sugerePersistenciaLocal) {
2736
+ if (!sugerePersistenciaLocal) {
2658
2737
  continue;
2659
2738
  }
2660
2739
  if ([...esperados.values()].some((item) => item.alvo === efeito.alvo)) {
@@ -2819,12 +2898,24 @@ export async function analisarDriftLegado(contexto, opcoes) {
2819
2898
  const taskPorChave = new Map();
2820
2899
  const guardrailsPorTask = new Map();
2821
2900
  const resumoVinculosPorTask = new Map();
2901
+ const arquivosAncoraHerdadosPorTask = new Map();
2822
2902
  for (const item of contexto.modulosSelecionados) {
2823
2903
  const ir = item.resultado.ir;
2824
2904
  if (!ir) {
2825
2905
  continue;
2826
2906
  }
2827
2907
  const superficiesPorChave = new Map(ir.superficies.map((superficie) => [`${superficie.tipo}:${superficie.nome}`, superficie]));
2908
+ const routesPorNome = new Map(ir.routes.map((route) => [route.nome, route]));
2909
+ const flowsPorNome = new Map(ir.flows.map((flow) => [flow.nome, flow]));
2910
+ const registrarArquivoAncoraHerdado = (taskNome, arquivo) => {
2911
+ if (!arquivo) {
2912
+ return;
2913
+ }
2914
+ const chaveTask = `${ir.nome}:${taskNome}`;
2915
+ const arquivos = arquivosAncoraHerdadosPorTask.get(chaveTask) ?? new Set();
2916
+ arquivos.add(arquivo);
2917
+ arquivosAncoraHerdadosPorTask.set(chaveTask, arquivos);
2918
+ };
2828
2919
  for (const task of ir.tasks) {
2829
2920
  guardrailsPorTask.set(`${ir.nome}:${task.nome}`, {
2830
2921
  publica: false,
@@ -2950,16 +3041,18 @@ export async function analisarDriftLegado(contexto, opcoes) {
2950
3041
  confiancaVinculo: "baixa",
2951
3042
  riscoOperacional: "baixo",
2952
3043
  lacunas: [],
3044
+ ancoragemVinculo: "ausente",
2953
3045
  arquivosReferenciados: [...arquivosReferenciados].sort((a, b) => a.localeCompare(b, "pt-BR")),
3046
+ arquivosAncoraHerdados: [],
2954
3047
  arquivosProvaveisEditar: [],
2955
3048
  simbolosReferenciados: [...simbolosReferenciados].sort((a, b) => a.localeCompare(b, "pt-BR")),
2956
3049
  candidatosImpl: ordenarCandidatos([...candidatosTask.values()]).slice(0, 5),
2957
3050
  checksSugeridos: [],
2958
3051
  });
2959
- for (const recursoEsperado of extrairRecursosEsperados(task, ir)) {
3052
+ for (const recursoEsperado of extrairRecursosEsperados(task, ir, mapaRecursos, mapaImpl)) {
2960
3053
  let resolvido = resolverRecursoEsperado(mapaRecursos, recursoEsperado, arquivosReferenciados);
2961
3054
  if (!resolvido) {
2962
- resolvido = resolverPersistenciaLocalPorTask(mapaRecursos, task, ir, recursoEsperado)[0];
3055
+ resolvido = resolverPersistenciaLocalPorTask(mapaRecursos, task, ir, recursoEsperado, mapaImpl)[0];
2963
3056
  }
2964
3057
  const registro = {
2965
3058
  modulo: ir.nome,
@@ -3155,6 +3248,29 @@ export async function analisarDriftLegado(contexto, opcoes) {
3155
3248
  }
3156
3249
  else {
3157
3250
  vinculosValidos.push(registro);
3251
+ if (itemVinculo.donoTipo === "modulo") {
3252
+ for (const task of ir.tasks) {
3253
+ registrarArquivoAncoraHerdado(task.nome, registro.arquivo);
3254
+ }
3255
+ }
3256
+ else if (itemVinculo.donoTipo === "flow") {
3257
+ const flow = flowsPorNome.get(itemVinculo.dono);
3258
+ for (const taskNome of flow?.tasksReferenciadas ?? []) {
3259
+ registrarArquivoAncoraHerdado(taskNome, registro.arquivo);
3260
+ }
3261
+ }
3262
+ else if (itemVinculo.donoTipo === "route") {
3263
+ const route = routesPorNome.get(itemVinculo.dono);
3264
+ if (route?.task) {
3265
+ registrarArquivoAncoraHerdado(route.task, registro.arquivo);
3266
+ }
3267
+ }
3268
+ else if (itemVinculo.donoTipo === "superficie") {
3269
+ const superficie = superficiesPorChave.get(itemVinculo.dono);
3270
+ if (superficie?.task) {
3271
+ registrarArquivoAncoraHerdado(superficie.task, registro.arquivo);
3272
+ }
3273
+ }
3158
3274
  }
3159
3275
  if (itemVinculo.donoTipo === "task") {
3160
3276
  const chaveTask = `${ir.nome}:${itemVinculo.dono}`;
@@ -3197,6 +3313,9 @@ export async function analisarDriftLegado(contexto, opcoes) {
3197
3313
  quebrados: 0,
3198
3314
  arquivos: new Set(),
3199
3315
  };
3316
+ const arquivosAncoraHerdados = [...(arquivosAncoraHerdadosPorTask.get(chaveTask) ?? new Set())]
3317
+ .filter((arquivo) => !resumoVinculos.arquivos.has(arquivo))
3318
+ .sort((a, b) => a.localeCompare(b, "pt-BR"));
3200
3319
  if (!task) {
3201
3320
  continue;
3202
3321
  }
@@ -3204,8 +3323,15 @@ export async function analisarDriftLegado(contexto, opcoes) {
3204
3323
  resumo.riscoOperacional = calcularRiscoOperacional(task);
3205
3324
  resumo.lacunas = resumirLacunasTask(task, resumo.semImplementacao, resumo.implsQuebrados, resumoVinculos.quebrados, guardrails);
3206
3325
  resumo.scoreSemantico = calcularScoreTask(task, resumo.implsValidos, resumo.implsQuebrados, resumoVinculos.validos, resumoVinculos.quebrados, resumo.semImplementacao);
3326
+ resumo.ancoragemVinculo = task.vinculos.length > 0
3327
+ ? "propria"
3328
+ : arquivosAncoraHerdados.length > 0
3329
+ ? "herdada_modulo"
3330
+ : "ausente";
3331
+ resumo.arquivosAncoraHerdados = arquivosAncoraHerdados;
3207
3332
  resumo.arquivosProvaveisEditar = [...new Set([
3208
3333
  ...resumo.arquivosReferenciados,
3334
+ ...arquivosAncoraHerdados,
3209
3335
  ...resumo.candidatosImpl.map((candidato) => candidato.arquivo),
3210
3336
  ...resumoVinculos.arquivos,
3211
3337
  ])].sort((a, b) => a.localeCompare(b, "pt-BR"));
@@ -3312,7 +3438,7 @@ export async function analisarDriftLegado(contexto, opcoes) {
3312
3438
  const appRoutes = [...new Set(consumerSurfaces.map((surface) => surface.rota))]
3313
3439
  .sort((a, b) => a.localeCompare(b, "pt-BR"));
3314
3440
  const consumerFramework = inferirConsumerFrameworkPrincipal(contexto.fontesLegado, consumerSurfaces, consumerBridges);
3315
- const persistenciaReal = await analisarPersistenciaReal(contexto, mapaRecursos, detalhesPersistencia, opcoesResolvidas);
3441
+ const persistenciaReal = await analisarPersistenciaReal(contexto, mapaRecursos, detalhesPersistencia, opcoesResolvidas, mapaImpl);
3316
3442
  for (const item of persistenciaReal) {
3317
3443
  if (item.status === "divergente") {
3318
3444
  diagnosticos.push({