@semacode/cli 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +272 -0
- package/LICENSE +22 -0
- package/README.md +2 -2
- package/SEMA_BRIEF.curto.txt +9 -0
- package/SEMA_BRIEF.md +49 -0
- package/SEMA_BRIEF.micro.txt +7 -0
- package/SEMA_INDEX.json +546 -0
- package/dist/cpp-symbols.d.ts +10 -0
- package/dist/cpp-symbols.js.map +1 -0
- package/dist/dotnet-http.d.ts +23 -0
- package/dist/dotnet-http.js.map +1 -0
- package/dist/drift.d.ts +118 -0
- package/dist/drift.js.map +1 -0
- package/dist/go-http.d.ts +23 -0
- package/dist/go-http.js.map +1 -0
- package/dist/importador.d.ts +29 -0
- package/dist/importador.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js.map +1 -0
- package/dist/java-http.d.ts +23 -0
- package/dist/java-http.js.map +1 -0
- package/dist/lua-symbols.d.ts +8 -0
- package/dist/lua-symbols.js.map +1 -0
- package/dist/projeto.d.ts +48 -0
- package/dist/projeto.js.map +1 -0
- package/dist/python-http.d.ts +23 -0
- package/dist/python-http.js.map +1 -0
- package/dist/rust-http.d.ts +23 -0
- package/dist/rust-http.js.map +1 -0
- package/dist/tipos.d.ts +3 -0
- package/dist/tipos.js.map +1 -0
- package/dist/typescript-http.d.ts +35 -0
- package/dist/typescript-http.js.map +1 -0
- package/docs/AGENT_STARTER.md +102 -0
- package/docs/como-ensinar-a-sema-para-ia.md +149 -0
- package/docs/fluxo-pratico-ia-sema.md +177 -0
- package/docs/instalacao-e-primeiro-uso.md +196 -0
- package/docs/integracao-com-ia.md +228 -0
- package/docs/pagamento-ponta-a-ponta.md +155 -0
- package/docs/prompt-base-ia-sema.md +104 -0
- package/docs/sintaxe.md +361 -0
- package/exemplos/agendamento.sema +106 -0
- package/exemplos/assinatura.sema +136 -0
- package/exemplos/auditoria.sema +88 -0
- package/exemplos/autenticacao.sema +125 -0
- package/exemplos/automacao.sema +107 -0
- package/exemplos/cadastro_usuario.sema +54 -0
- package/exemplos/calculadora.sema +78 -0
- package/exemplos/crud_simples.sema +89 -0
- package/exemplos/estoque.sema +126 -0
- package/exemplos/exportacao.sema +94 -0
- package/exemplos/fila.sema +131 -0
- package/exemplos/integracao_externa.sema +94 -0
- package/exemplos/multi_tenant.sema +140 -0
- package/exemplos/notificacao.sema +98 -0
- package/exemplos/operacao_estrategia.sema +402 -0
- package/exemplos/pagamento.sema +222 -0
- package/exemplos/pagamento_dominio.sema +35 -0
- package/exemplos/pedido.sema +119 -0
- package/exemplos/permissao.sema +121 -0
- package/exemplos/relatorio.sema +93 -0
- package/exemplos/testes_embutidos.sema +45 -0
- package/exemplos/tratamento_erro.sema +157 -0
- package/exemplos/upload_arquivo.sema +93 -0
- package/exemplos/webhook.sema +96 -0
- package/llms-full.txt +34 -0
- package/llms.txt +17 -0
- package/node_modules/@sema/gerador-css/dist/index.d.ts +3 -0
- package/node_modules/@sema/gerador-css/dist/index.js +592 -0
- package/node_modules/@sema/gerador-css/dist/index.js.map +1 -0
- package/node_modules/@sema/gerador-css/package.json +7 -0
- package/node_modules/@sema/gerador-dart/dist/index.d.ts +3 -0
- package/node_modules/@sema/gerador-dart/dist/index.js +44 -0
- package/node_modules/@sema/gerador-dart/dist/index.js.map +1 -0
- package/node_modules/@sema/gerador-dart/package.json +7 -0
- package/node_modules/@sema/gerador-html/dist/index.d.ts +3 -0
- package/node_modules/@sema/gerador-html/dist/index.js +163 -0
- package/node_modules/@sema/gerador-html/dist/index.js.map +1 -0
- package/node_modules/@sema/gerador-html/package.json +7 -0
- package/node_modules/@sema/gerador-javascript/dist/index.d.ts +3 -0
- package/node_modules/@sema/gerador-javascript/dist/index.js +421 -0
- package/node_modules/@sema/gerador-javascript/dist/index.js.map +1 -0
- package/node_modules/@sema/gerador-javascript/package.json +7 -0
- package/node_modules/@sema/gerador-lua/dist/index.d.ts +3 -0
- package/node_modules/@sema/gerador-lua/dist/index.js +328 -0
- package/node_modules/@sema/gerador-lua/dist/index.js.map +1 -0
- package/node_modules/@sema/gerador-lua/package.json +7 -0
- package/node_modules/@sema/gerador-python/dist/index.d.ts +6 -0
- package/node_modules/@sema/gerador-python/dist/index.js +628 -0
- package/node_modules/@sema/gerador-python/dist/index.js.map +1 -0
- package/node_modules/@sema/gerador-python/package.json +7 -0
- package/node_modules/@sema/gerador-typescript/dist/index.d.ts +6 -0
- package/node_modules/@sema/gerador-typescript/dist/index.js +656 -0
- package/node_modules/@sema/gerador-typescript/dist/index.js.map +1 -0
- package/node_modules/@sema/gerador-typescript/package.json +7 -0
- package/node_modules/@sema/nucleo/dist/ast/tipos.d.ts +122 -0
- package/node_modules/@sema/nucleo/dist/ast/tipos.js +2 -0
- package/node_modules/@sema/nucleo/dist/ast/tipos.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/diagnosticos/index.d.ts +21 -0
- package/node_modules/@sema/nucleo/dist/diagnosticos/index.js +12 -0
- package/node_modules/@sema/nucleo/dist/diagnosticos/index.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/formatador/index.d.ts +9 -0
- package/node_modules/@sema/nucleo/dist/formatador/index.js +445 -0
- package/node_modules/@sema/nucleo/dist/formatador/index.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/index.d.ts +34 -0
- package/node_modules/@sema/nucleo/dist/index.js +95 -0
- package/node_modules/@sema/nucleo/dist/index.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/ir/conversor.d.ts +5 -0
- package/node_modules/@sema/nucleo/dist/ir/conversor.js +781 -0
- package/node_modules/@sema/nucleo/dist/ir/conversor.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/ir/modelos.d.ts +285 -0
- package/node_modules/@sema/nucleo/dist/ir/modelos.js +2 -0
- package/node_modules/@sema/nucleo/dist/ir/modelos.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/lexer/lexer.d.ts +7 -0
- package/node_modules/@sema/nucleo/dist/lexer/lexer.js +122 -0
- package/node_modules/@sema/nucleo/dist/lexer/lexer.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/lexer/tokens.d.ts +8 -0
- package/node_modules/@sema/nucleo/dist/lexer/tokens.js +46 -0
- package/node_modules/@sema/nucleo/dist/lexer/tokens.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/parser/parser.d.ts +9 -0
- package/node_modules/@sema/nucleo/dist/parser/parser.js +656 -0
- package/node_modules/@sema/nucleo/dist/parser/parser.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/semantico/analisador.d.ts +57 -0
- package/node_modules/@sema/nucleo/dist/semantico/analisador.js +1497 -0
- package/node_modules/@sema/nucleo/dist/semantico/analisador.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/semantico/estruturas.d.ts +104 -0
- package/node_modules/@sema/nucleo/dist/semantico/estruturas.js +445 -0
- package/node_modules/@sema/nucleo/dist/semantico/estruturas.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/semantico/seguranca.d.ts +91 -0
- package/node_modules/@sema/nucleo/dist/semantico/seguranca.js +258 -0
- package/node_modules/@sema/nucleo/dist/semantico/seguranca.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/util/arquivos.d.ts +2 -0
- package/node_modules/@sema/nucleo/dist/util/arquivos.js +25 -0
- package/node_modules/@sema/nucleo/dist/util/arquivos.js.map +1 -0
- package/node_modules/@sema/nucleo/package.json +7 -0
- package/node_modules/@sema/padroes/dist/index.d.ts +23 -0
- package/node_modules/@sema/padroes/dist/index.js +210 -0
- package/node_modules/@sema/padroes/dist/index.js.map +1 -0
- package/node_modules/@sema/padroes/package.json +7 -0
- package/package.json +40 -17
- package/src/cpp-symbols.ts +0 -82
- package/src/dotnet-http.ts +0 -355
- package/src/drift.ts +0 -2455
- package/src/go-http.ts +0 -118
- package/src/importador.ts +0 -3448
- package/src/index.ts +0 -4470
- package/src/java-http.ts +0 -247
- package/src/projeto.ts +0 -810
- package/src/python-http.ts +0 -258
- package/src/rust-http.ts +0 -125
- package/src/tipos.ts +0 -22
- package/src/typescript-http.ts +0 -1076
- package/tsconfig.json +0 -20
package/src/drift.ts
DELETED
|
@@ -1,2455 +0,0 @@
|
|
|
1
|
-
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import ts from "typescript";
|
|
4
|
-
import type { IrFlow, IrModulo, IrRoute, IrSuperficie, IrTask, IrVinculo, NivelConfiancaSemantica, NivelRiscoSemantico } from "@sema/nucleo";
|
|
5
|
-
import type { ContextoProjetoCarregado } from "./projeto.js";
|
|
6
|
-
import type { FonteLegado } from "./tipos.js";
|
|
7
|
-
import { extrairSimbolosCpp } from "./cpp-symbols.js";
|
|
8
|
-
import { extrairRotasDotnet, extrairSimbolosDotnet } from "./dotnet-http.js";
|
|
9
|
-
import { extrairRotasGo, extrairSimbolosGo } from "./go-http.js";
|
|
10
|
-
import { extrairRotasJava, extrairSimbolosJava } from "./java-http.js";
|
|
11
|
-
import { contarIndentacaoPython, extrairRotasFlaskDecoradas, normalizarCaminhoFlask } from "./python-http.js";
|
|
12
|
-
import { extrairRotasRust, extrairSimbolosRust } from "./rust-http.js";
|
|
13
|
-
import { extrairRotasTypeScriptHttp } from "./typescript-http.js";
|
|
14
|
-
|
|
15
|
-
interface SimboloResolvido {
|
|
16
|
-
origem: "ts" | "py" | "dart" | "cs" | "java" | "go" | "rust" | "cpp";
|
|
17
|
-
caminho: string;
|
|
18
|
-
arquivo: string;
|
|
19
|
-
simbolo: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
type ConsumerFramework = "nextjs-consumer" | "react-vite-consumer" | "angular-consumer" | "flutter-consumer";
|
|
23
|
-
|
|
24
|
-
interface RotaResolvida {
|
|
25
|
-
origem: "nestjs" | "fastapi" | "flask" | "nextjs" | ConsumerFramework | "firebase" | "dotnet" | "java" | "go" | "rust";
|
|
26
|
-
metodo: string;
|
|
27
|
-
caminho: string;
|
|
28
|
-
arquivo: string;
|
|
29
|
-
simbolo: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface RegistroConsumerSurfaceDrift {
|
|
33
|
-
rota: string;
|
|
34
|
-
arquivo: string;
|
|
35
|
-
tipoArquivo: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface RegistroConsumerBridgeDrift {
|
|
39
|
-
caminho: string;
|
|
40
|
-
arquivo: string;
|
|
41
|
-
simbolo: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface DiagnosticoDrift {
|
|
45
|
-
tipo: "impl_quebrado" | "task_sem_impl" | "rota_divergente" | "recurso_divergente" | "vinculo_quebrado" | "seguranca_frouxa";
|
|
46
|
-
modulo: string;
|
|
47
|
-
task?: string;
|
|
48
|
-
route?: string;
|
|
49
|
-
mensagem: string;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
interface RegistroImplDrift {
|
|
53
|
-
modulo: string;
|
|
54
|
-
task: string;
|
|
55
|
-
origem: "ts" | "py" | "dart" | "cs" | "java" | "go" | "rust" | "cpp";
|
|
56
|
-
caminho: string;
|
|
57
|
-
arquivo?: string;
|
|
58
|
-
simbolo?: string;
|
|
59
|
-
caminhoResolvido?: string;
|
|
60
|
-
status: "resolvido" | "quebrado";
|
|
61
|
-
candidatos?: SimboloCandidatoDrift[];
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
interface RegistroRotaDivergente {
|
|
65
|
-
modulo: string;
|
|
66
|
-
route: string;
|
|
67
|
-
metodo?: string;
|
|
68
|
-
caminho?: string;
|
|
69
|
-
motivo: string;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
interface RecursoResolvido {
|
|
73
|
-
origem: "firebase";
|
|
74
|
-
nome: string;
|
|
75
|
-
arquivo: string;
|
|
76
|
-
simbolo?: string;
|
|
77
|
-
tipo: "colecao";
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
interface RegistroRecursoDrift {
|
|
81
|
-
modulo: string;
|
|
82
|
-
task: string;
|
|
83
|
-
categoria: "persistencia";
|
|
84
|
-
alvo: string;
|
|
85
|
-
arquivo: string;
|
|
86
|
-
origem: "firebase";
|
|
87
|
-
tipo: "colecao";
|
|
88
|
-
status: "resolvido" | "divergente";
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
interface SimboloCandidatoDrift {
|
|
92
|
-
origem: "ts" | "py" | "dart" | "cs" | "java" | "go" | "rust" | "cpp";
|
|
93
|
-
caminho: string;
|
|
94
|
-
arquivo: string;
|
|
95
|
-
simbolo: string;
|
|
96
|
-
confianca: "alta" | "media";
|
|
97
|
-
motivo: string;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
interface ResumoTaskDrift {
|
|
101
|
-
modulo: string;
|
|
102
|
-
task: string;
|
|
103
|
-
impls: number;
|
|
104
|
-
implsValidos: number;
|
|
105
|
-
implsQuebrados: number;
|
|
106
|
-
semImplementacao: boolean;
|
|
107
|
-
scoreSemantico: number;
|
|
108
|
-
confiancaVinculo: NivelConfiancaSemantica;
|
|
109
|
-
riscoOperacional: NivelRiscoSemantico;
|
|
110
|
-
lacunas: string[];
|
|
111
|
-
arquivosReferenciados: string[];
|
|
112
|
-
arquivosProvaveisEditar: string[];
|
|
113
|
-
simbolosReferenciados: string[];
|
|
114
|
-
candidatosImpl: SimboloCandidatoDrift[];
|
|
115
|
-
checksSugeridos: string[];
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
interface RegistroVinculoDrift {
|
|
119
|
-
modulo: string;
|
|
120
|
-
donoTipo: "modulo" | "task" | "flow" | "route" | "superficie";
|
|
121
|
-
dono: string;
|
|
122
|
-
tipo: string;
|
|
123
|
-
valor: string;
|
|
124
|
-
arquivo?: string;
|
|
125
|
-
simbolo?: string;
|
|
126
|
-
status: "resolvido" | "parcial" | "nao_encontrado";
|
|
127
|
-
confianca: NivelConfiancaSemantica;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export interface ResultadoDrift {
|
|
131
|
-
comando: "drift";
|
|
132
|
-
sucesso: boolean;
|
|
133
|
-
consumerFramework: ConsumerFramework | null;
|
|
134
|
-
appRoutes: string[];
|
|
135
|
-
consumerSurfaces: RegistroConsumerSurfaceDrift[];
|
|
136
|
-
consumerBridges: RegistroConsumerBridgeDrift[];
|
|
137
|
-
modulos: Array<{
|
|
138
|
-
caminho: string;
|
|
139
|
-
modulo: string | null;
|
|
140
|
-
tasks: number;
|
|
141
|
-
routes: number;
|
|
142
|
-
}>;
|
|
143
|
-
tasks: ResumoTaskDrift[];
|
|
144
|
-
impls_validos: RegistroImplDrift[];
|
|
145
|
-
impls_quebrados: RegistroImplDrift[];
|
|
146
|
-
vinculos_validos: RegistroVinculoDrift[];
|
|
147
|
-
vinculos_quebrados: RegistroVinculoDrift[];
|
|
148
|
-
rotas_divergentes: RegistroRotaDivergente[];
|
|
149
|
-
recursos_validos: RegistroRecursoDrift[];
|
|
150
|
-
recursos_divergentes: RegistroRecursoDrift[];
|
|
151
|
-
resumo_operacional: {
|
|
152
|
-
scoreMedio: number;
|
|
153
|
-
confiancaGeral: NivelConfiancaSemantica;
|
|
154
|
-
riscosPrincipais: string[];
|
|
155
|
-
oQueTocar: string[];
|
|
156
|
-
oQueValidar: string[];
|
|
157
|
-
oQueEstaFrouxo: string[];
|
|
158
|
-
oQueFoiInferido: string[];
|
|
159
|
-
};
|
|
160
|
-
diagnosticos: DiagnosticoDrift[];
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const DIRETORIOS_IGNORADOS = new Set([
|
|
164
|
-
".git",
|
|
165
|
-
".hg",
|
|
166
|
-
".svn",
|
|
167
|
-
"node_modules",
|
|
168
|
-
"dist",
|
|
169
|
-
"build",
|
|
170
|
-
".next",
|
|
171
|
-
".nuxt",
|
|
172
|
-
".dart_tool",
|
|
173
|
-
"__pycache__",
|
|
174
|
-
".venv",
|
|
175
|
-
"venv",
|
|
176
|
-
"coverage",
|
|
177
|
-
".tmp",
|
|
178
|
-
"generated",
|
|
179
|
-
]);
|
|
180
|
-
|
|
181
|
-
function normalizarFragmentoArquivo(valor: string): string {
|
|
182
|
-
return valor.replace(/\\/g, "/").replace(/^\.?\//, "").trim().toLowerCase();
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function escolherArquivoPorVinculo(arquivos: string[], valor: string): { arquivo?: string; confianca: NivelConfiancaSemantica; status: RegistroVinculoDrift["status"] } {
|
|
186
|
-
const normalizado = normalizarFragmentoArquivo(valor);
|
|
187
|
-
const exato = arquivos.find((arquivo) => normalizarFragmentoArquivo(arquivo) === normalizado);
|
|
188
|
-
if (exato) {
|
|
189
|
-
return { arquivo: exato, confianca: "alta", status: "resolvido" };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const porSufixo = arquivos.find((arquivo) => normalizarFragmentoArquivo(arquivo).endsWith(normalizado));
|
|
193
|
-
if (porSufixo) {
|
|
194
|
-
return { arquivo: porSufixo, confianca: "media", status: "parcial" };
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return { confianca: "baixa", status: "nao_encontrado" };
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function escolherSimboloPorVinculo(
|
|
201
|
-
simbolos: SimboloResolvido[],
|
|
202
|
-
mapaImpl: Map<string, SimboloResolvido>,
|
|
203
|
-
valor: string,
|
|
204
|
-
): { simbolo?: SimboloResolvido; confianca: NivelConfiancaSemantica; status: RegistroVinculoDrift["status"] } {
|
|
205
|
-
const exato = mapaImpl.get(valor);
|
|
206
|
-
if (exato) {
|
|
207
|
-
return { simbolo: exato, confianca: "alta", status: "resolvido" };
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const ultimoSegmento = valor.split(".").at(-1)?.toLowerCase();
|
|
211
|
-
const aproximado = simbolos.find((simbolo) =>
|
|
212
|
-
simbolo.caminho.toLowerCase() === valor.toLowerCase()
|
|
213
|
-
|| simbolo.simbolo.toLowerCase() === ultimoSegmento
|
|
214
|
-
|| simbolo.caminho.toLowerCase().endsWith(`.${ultimoSegmento}`));
|
|
215
|
-
|
|
216
|
-
if (aproximado) {
|
|
217
|
-
return { simbolo: aproximado, confianca: "media", status: "parcial" };
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return { confianca: "baixa", status: "nao_encontrado" };
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function resolverArquivoOuSimboloAncora(
|
|
224
|
-
vinculos: IrVinculo[],
|
|
225
|
-
simbolos: SimboloResolvido[],
|
|
226
|
-
mapaImpl: Map<string, SimboloResolvido>,
|
|
227
|
-
arquivos: string[],
|
|
228
|
-
): { arquivo?: string; simbolo?: string; confianca: NivelConfiancaSemantica } | undefined {
|
|
229
|
-
for (const vinculo of vinculos) {
|
|
230
|
-
if (vinculo.simbolo) {
|
|
231
|
-
const resolucaoSimbolo = escolherSimboloPorVinculo(simbolos, mapaImpl, vinculo.simbolo);
|
|
232
|
-
if (resolucaoSimbolo.status !== "nao_encontrado") {
|
|
233
|
-
return {
|
|
234
|
-
arquivo: resolucaoSimbolo.simbolo?.arquivo,
|
|
235
|
-
simbolo: resolucaoSimbolo.simbolo?.simbolo,
|
|
236
|
-
confianca: resolucaoSimbolo.confianca,
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (vinculo.arquivo) {
|
|
242
|
-
const resolucaoArquivo = escolherArquivoPorVinculo(arquivos, vinculo.arquivo);
|
|
243
|
-
if (resolucaoArquivo.status !== "nao_encontrado") {
|
|
244
|
-
return {
|
|
245
|
-
arquivo: resolucaoArquivo.arquivo,
|
|
246
|
-
confianca: resolucaoArquivo.confianca,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return undefined;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function encontrarAncoraSuperficie(
|
|
256
|
-
ir: IrModulo,
|
|
257
|
-
superficie: IrSuperficie,
|
|
258
|
-
simbolos: SimboloResolvido[],
|
|
259
|
-
mapaImpl: Map<string, SimboloResolvido>,
|
|
260
|
-
arquivos: string[],
|
|
261
|
-
): { arquivo?: string; simbolo?: string; confianca: NivelConfiancaSemantica } | undefined {
|
|
262
|
-
const ancoraDireta = resolverArquivoOuSimboloAncora(superficie.vinculos, simbolos, mapaImpl, arquivos);
|
|
263
|
-
if (ancoraDireta) {
|
|
264
|
-
return ancoraDireta;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
for (const impl of superficie.implementacoesExternas) {
|
|
268
|
-
const resolvido = mapaImpl.get(impl.caminho);
|
|
269
|
-
if (resolvido) {
|
|
270
|
-
return {
|
|
271
|
-
arquivo: resolvido.arquivo,
|
|
272
|
-
simbolo: resolvido.simbolo,
|
|
273
|
-
confianca: "alta",
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (!superficie.task) {
|
|
279
|
-
return undefined;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const taskAssociada = ir.tasks.find((task) => task.nome === superficie.task);
|
|
283
|
-
if (!taskAssociada) {
|
|
284
|
-
return undefined;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const ancoraTask = resolverArquivoOuSimboloAncora(taskAssociada.vinculos, simbolos, mapaImpl, arquivos);
|
|
288
|
-
if (ancoraTask) {
|
|
289
|
-
return ancoraTask;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
for (const impl of taskAssociada.implementacoesExternas) {
|
|
293
|
-
const resolvido = mapaImpl.get(impl.caminho);
|
|
294
|
-
if (resolvido) {
|
|
295
|
-
return {
|
|
296
|
-
arquivo: resolvido.arquivo,
|
|
297
|
-
simbolo: resolvido.simbolo,
|
|
298
|
-
confianca: "alta",
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
return undefined;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function calcularRiscoOperacional(task: IrTask): NivelRiscoSemantico {
|
|
307
|
-
const dadosSensiveis = Boolean(
|
|
308
|
-
task.dados.classificacaoPadrao && ["pii", "financeiro", "credencial", "segredo"].includes(task.dados.classificacaoPadrao)
|
|
309
|
-
|| task.dados.campos.some((campo) => ["pii", "financeiro", "credencial", "segredo"].includes(campo.classificacao))
|
|
310
|
-
);
|
|
311
|
-
const efeitoPrivilegiado = task.efeitosEstruturados.some((efeito) =>
|
|
312
|
-
["db.read", "db.write", "queue.publish", "queue.consume", "fs.read", "fs.write", "network.egress", "secret.read", "shell.exec"].includes(efeito.categoria)
|
|
313
|
-
|| ["alta", "critica"].includes(efeito.criticidade ?? ""),
|
|
314
|
-
);
|
|
315
|
-
if (
|
|
316
|
-
task.execucao.criticidadeOperacional === "alta"
|
|
317
|
-
|| task.execucao.criticidadeOperacional === "critica"
|
|
318
|
-
|| dadosSensiveis
|
|
319
|
-
|| efeitoPrivilegiado
|
|
320
|
-
|| task.efeitosEstruturados.some((efeito) => efeito.categoria === "persistencia" || efeito.criticidade === "critica")
|
|
321
|
-
) {
|
|
322
|
-
return "alto";
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
if (task.efeitosEstruturados.length > 0 || task.vinculos.length > 0 || task.errosDetalhados.length > 0) {
|
|
326
|
-
return "medio";
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return "baixo";
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function calcularConfiancaTask(
|
|
333
|
-
task: IrTask,
|
|
334
|
-
implsValidos: number,
|
|
335
|
-
implsQuebrados: number,
|
|
336
|
-
vinculosValidos: number,
|
|
337
|
-
vinculosQuebrados: number,
|
|
338
|
-
): NivelConfiancaSemantica {
|
|
339
|
-
if ((implsValidos > 0 || vinculosValidos > 0) && implsQuebrados === 0 && vinculosQuebrados === 0) {
|
|
340
|
-
return "alta";
|
|
341
|
-
}
|
|
342
|
-
if (implsValidos > 0 || vinculosValidos > 0 || task.implementacoesExternas.length > 0 || task.vinculos.length > 0) {
|
|
343
|
-
return "media";
|
|
344
|
-
}
|
|
345
|
-
return "baixa";
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
function calcularScoreTask(
|
|
349
|
-
task: IrTask,
|
|
350
|
-
implsValidos: number,
|
|
351
|
-
implsQuebrados: number,
|
|
352
|
-
vinculosValidos: number,
|
|
353
|
-
vinculosQuebrados: number,
|
|
354
|
-
semImplementacao: boolean,
|
|
355
|
-
): number {
|
|
356
|
-
let score = 45;
|
|
357
|
-
if (!semImplementacao && task.implementacoesExternas.length > 0) {
|
|
358
|
-
score += 15;
|
|
359
|
-
}
|
|
360
|
-
score += Math.min(implsValidos * 10, 20);
|
|
361
|
-
score -= Math.min(implsQuebrados * 20, 30);
|
|
362
|
-
score += Math.min(vinculosValidos * 5, 15);
|
|
363
|
-
score -= Math.min(vinculosQuebrados * 10, 20);
|
|
364
|
-
if (task.guarantees.length > 0) {
|
|
365
|
-
score += 5;
|
|
366
|
-
}
|
|
367
|
-
if (task.execucao.explicita) {
|
|
368
|
-
score += 5;
|
|
369
|
-
}
|
|
370
|
-
return Math.max(0, Math.min(100, score));
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function resumirLacunasTask(
|
|
374
|
-
task: IrTask,
|
|
375
|
-
semImplementacao: boolean,
|
|
376
|
-
implsQuebrados: number,
|
|
377
|
-
vinculosQuebrados: number,
|
|
378
|
-
guardrails: {
|
|
379
|
-
publica: boolean;
|
|
380
|
-
sensivel: boolean;
|
|
381
|
-
auth: boolean;
|
|
382
|
-
authz: boolean;
|
|
383
|
-
dados: boolean;
|
|
384
|
-
audit: boolean;
|
|
385
|
-
segredos: boolean;
|
|
386
|
-
forbidden: boolean;
|
|
387
|
-
dadosSensiveis: boolean;
|
|
388
|
-
efeitoPrivilegiado: boolean;
|
|
389
|
-
exigeSegredos: boolean;
|
|
390
|
-
},
|
|
391
|
-
): string[] {
|
|
392
|
-
const lacunas: string[] = [];
|
|
393
|
-
if (semImplementacao) {
|
|
394
|
-
lacunas.push("sem_impl");
|
|
395
|
-
}
|
|
396
|
-
if (implsQuebrados > 0) {
|
|
397
|
-
lacunas.push("impl_quebrado");
|
|
398
|
-
}
|
|
399
|
-
if (task.vinculos.length === 0) {
|
|
400
|
-
lacunas.push("sem_vinculos");
|
|
401
|
-
}
|
|
402
|
-
if (vinculosQuebrados > 0) {
|
|
403
|
-
lacunas.push("vinculo_quebrado");
|
|
404
|
-
}
|
|
405
|
-
if (!task.execucao.explicita) {
|
|
406
|
-
lacunas.push("execucao_implicita");
|
|
407
|
-
}
|
|
408
|
-
if (guardrails.publica && !task.execucao.explicita) {
|
|
409
|
-
lacunas.push("superficie_publica_sem_execucao");
|
|
410
|
-
}
|
|
411
|
-
if (guardrails.sensivel && !task.execucao.explicita) {
|
|
412
|
-
lacunas.push("execucao_critica_sem_bloco");
|
|
413
|
-
}
|
|
414
|
-
if ((guardrails.publica || guardrails.sensivel) && semImplementacao && task.vinculos.length === 0) {
|
|
415
|
-
lacunas.push("rastreabilidade_fraca");
|
|
416
|
-
}
|
|
417
|
-
if (guardrails.publica && !guardrails.auth) {
|
|
418
|
-
lacunas.push("auth_ausente");
|
|
419
|
-
}
|
|
420
|
-
if ((guardrails.publica || guardrails.sensivel || guardrails.efeitoPrivilegiado || guardrails.dadosSensiveis) && !guardrails.authz) {
|
|
421
|
-
lacunas.push("authz_frouxa");
|
|
422
|
-
}
|
|
423
|
-
if ((guardrails.publica || guardrails.sensivel || guardrails.efeitoPrivilegiado) && !guardrails.dados) {
|
|
424
|
-
lacunas.push("dados_nao_classificados");
|
|
425
|
-
}
|
|
426
|
-
if ((guardrails.publica || guardrails.sensivel || guardrails.efeitoPrivilegiado || guardrails.dadosSensiveis) && !guardrails.audit) {
|
|
427
|
-
lacunas.push("audit_ausente");
|
|
428
|
-
}
|
|
429
|
-
if (guardrails.exigeSegredos && !guardrails.segredos) {
|
|
430
|
-
lacunas.push("segredo_sem_governanca");
|
|
431
|
-
}
|
|
432
|
-
if ((guardrails.efeitoPrivilegiado || guardrails.dadosSensiveis) && !guardrails.forbidden) {
|
|
433
|
-
lacunas.push("proibicoes_ausentes");
|
|
434
|
-
}
|
|
435
|
-
return lacunas;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
function resumirOperacional(resultado: Omit<ResultadoDrift, "comando" | "sucesso">): ResultadoDrift["resumo_operacional"] {
|
|
439
|
-
const scoreMedio = resultado.tasks.length > 0
|
|
440
|
-
? Math.round(resultado.tasks.reduce((total, task) => total + task.scoreSemantico, 0) / resultado.tasks.length)
|
|
441
|
-
: 0;
|
|
442
|
-
const confiancaGeral: NivelConfiancaSemantica = scoreMedio >= 80 ? "alta" : scoreMedio >= 55 ? "media" : "baixa";
|
|
443
|
-
const riscosPrincipais = [...new Set(resultado.tasks.filter((task) => task.riscoOperacional !== "baixo").map((task) => `${task.task}:${task.riscoOperacional}`))];
|
|
444
|
-
const oQueTocar = [...new Set(resultado.tasks.flatMap((task) => task.arquivosProvaveisEditar))].slice(0, 20);
|
|
445
|
-
const oQueValidar = [...new Set(resultado.tasks.flatMap((task) => task.checksSugeridos))];
|
|
446
|
-
const oQueEstaFrouxo = [...new Set(resultado.tasks.flatMap((task) => task.lacunas))];
|
|
447
|
-
const oQueFoiInferido = [
|
|
448
|
-
...new Set([
|
|
449
|
-
...resultado.impls_quebrados.flatMap((impl) => impl.candidatos?.map((candidato) => candidato.caminho) ?? []),
|
|
450
|
-
...resultado.vinculos_quebrados.filter((vinculo) => vinculo.status === "parcial").map((vinculo) => `${vinculo.dono}:${vinculo.valor}`),
|
|
451
|
-
]),
|
|
452
|
-
];
|
|
453
|
-
|
|
454
|
-
return {
|
|
455
|
-
scoreMedio,
|
|
456
|
-
confiancaGeral,
|
|
457
|
-
riscosPrincipais,
|
|
458
|
-
oQueTocar,
|
|
459
|
-
oQueValidar,
|
|
460
|
-
oQueEstaFrouxo,
|
|
461
|
-
oQueFoiInferido,
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
function paraIdentificadorModulo(valor: string): string {
|
|
466
|
-
return valor
|
|
467
|
-
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
468
|
-
.replace(/[^A-Za-z0-9_]+/g, "_")
|
|
469
|
-
.replace(/_{2,}/g, "_")
|
|
470
|
-
.replace(/^_+|_+$/g, "")
|
|
471
|
-
.toLowerCase();
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function extrairTextoLiteral(expr?: ts.Expression): string | undefined {
|
|
475
|
-
if (!expr) {
|
|
476
|
-
return undefined;
|
|
477
|
-
}
|
|
478
|
-
if (ts.isStringLiteralLike(expr) || ts.isNoSubstitutionTemplateLiteral(expr)) {
|
|
479
|
-
return expr.text;
|
|
480
|
-
}
|
|
481
|
-
if (ts.isNumericLiteral(expr)) {
|
|
482
|
-
return expr.text;
|
|
483
|
-
}
|
|
484
|
-
return undefined;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
function listarDecoradores(node: ts.Node): readonly ts.Decorator[] {
|
|
488
|
-
return ts.canHaveDecorators(node) ? ts.getDecorators(node) ?? [] : [];
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
function lerDecorator(node: ts.Node, nomes: string[]): { nome: string; argumentos: ts.NodeArray<ts.Expression> } | undefined {
|
|
492
|
-
for (const decorator of listarDecoradores(node)) {
|
|
493
|
-
const expressao = decorator.expression;
|
|
494
|
-
if (ts.isCallExpression(expressao)) {
|
|
495
|
-
const alvo = expressao.expression;
|
|
496
|
-
if (ts.isIdentifier(alvo) && nomes.includes(alvo.text)) {
|
|
497
|
-
return { nome: alvo.text, argumentos: expressao.arguments };
|
|
498
|
-
}
|
|
499
|
-
} else if (ts.isIdentifier(expressao) && nomes.includes(expressao.text)) {
|
|
500
|
-
return { nome: expressao.text, argumentos: ts.factory.createNodeArray() };
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
return undefined;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function juntarCaminhoHttp(base: string | undefined, sufixo: string | undefined): string {
|
|
507
|
-
const partes = [base ?? "", sufixo ?? ""]
|
|
508
|
-
.map((item) => item.trim())
|
|
509
|
-
.filter(Boolean)
|
|
510
|
-
.map((item) => item.replace(/^\/+|\/+$/g, ""));
|
|
511
|
-
|
|
512
|
-
const caminho = `/${partes.join("/")}`.replace(/\/+/g, "/");
|
|
513
|
-
return caminho === "//" ? "/" : caminho;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
async function listarArquivosRecursivos(diretorio: string, extensoes: string[]): Promise<string[]> {
|
|
517
|
-
let entradas;
|
|
518
|
-
try {
|
|
519
|
-
entradas = await readdir(diretorio, { withFileTypes: true, encoding: "utf8" });
|
|
520
|
-
} catch {
|
|
521
|
-
return [];
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
const encontrados: string[] = [];
|
|
525
|
-
for (const entrada of entradas) {
|
|
526
|
-
if (DIRETORIOS_IGNORADOS.has(entrada.name)) {
|
|
527
|
-
continue;
|
|
528
|
-
}
|
|
529
|
-
const caminhoAtual = path.join(diretorio, entrada.name);
|
|
530
|
-
if (entrada.isDirectory()) {
|
|
531
|
-
encontrados.push(...await listarArquivosRecursivos(caminhoAtual, extensoes));
|
|
532
|
-
continue;
|
|
533
|
-
}
|
|
534
|
-
if (extensoes.some((extensao) => entrada.name.endsWith(extensao))) {
|
|
535
|
-
encontrados.push(caminhoAtual);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
return encontrados.sort((a, b) => a.localeCompare(b, "pt-BR"));
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
function caminhosSimbolicos(baseDiretorio: string, arquivo: string): string[] {
|
|
543
|
-
const relativo = path.relative(baseDiretorio, arquivo).replace(/\.[^.]+$/, "");
|
|
544
|
-
const semPrefixo = relativo
|
|
545
|
-
.split(path.sep)
|
|
546
|
-
.map((segmento) => paraIdentificadorModulo(segmento))
|
|
547
|
-
.filter(Boolean)
|
|
548
|
-
.join(".");
|
|
549
|
-
const prefixo = paraIdentificadorModulo(path.basename(baseDiretorio));
|
|
550
|
-
const comPrefixo = prefixo ? [prefixo, semPrefixo].filter(Boolean).join(".") : semPrefixo;
|
|
551
|
-
return [...new Set([semPrefixo, comPrefixo].filter(Boolean))];
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
function registrarSimboloTypeScript(
|
|
555
|
-
simbolos: Map<string, SimboloResolvido>,
|
|
556
|
-
basesSimbolicas: string[],
|
|
557
|
-
arquivo: string,
|
|
558
|
-
nome: string,
|
|
559
|
-
nomeClasse?: string,
|
|
560
|
-
): void {
|
|
561
|
-
for (const baseSimbolica of basesSimbolicas) {
|
|
562
|
-
const caminho = nomeClasse
|
|
563
|
-
? `${baseSimbolica}.${nomeClasse}.${nome}`
|
|
564
|
-
: `${baseSimbolica}.${nome}`;
|
|
565
|
-
simbolos.set(caminho, {
|
|
566
|
-
origem: "ts",
|
|
567
|
-
caminho,
|
|
568
|
-
arquivo,
|
|
569
|
-
simbolo: nomeClasse ? `${nomeClasse}.${nome}` : nome,
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
function normalizarRelacaoConsumer(relacaoArquivo: string): string {
|
|
575
|
-
return relacaoArquivo.replace(/\\/g, "/");
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
function normalizarSegmentoRotaConsumer(segmento: string): string {
|
|
579
|
-
const opcionalCatchAll = segmento.match(/^\[\[\.\.\.([A-Za-z_]\w*)\]\]$/);
|
|
580
|
-
if (opcionalCatchAll) {
|
|
581
|
-
return `{${opcionalCatchAll[1]}}`;
|
|
582
|
-
}
|
|
583
|
-
const catchAll = segmento.match(/^\[\.\.\.([A-Za-z_]\w*)\]$/);
|
|
584
|
-
if (catchAll) {
|
|
585
|
-
return `{${catchAll[1]}}`;
|
|
586
|
-
}
|
|
587
|
-
const dinamico = segmento.match(/^\[([A-Za-z_]\w*)\]$/);
|
|
588
|
-
if (dinamico) {
|
|
589
|
-
return `{${dinamico[1]}}`;
|
|
590
|
-
}
|
|
591
|
-
return segmento;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
function montarRotaConsumer(partes: string[]): string {
|
|
595
|
-
const filtradas = partes
|
|
596
|
-
.filter((segmento) => segmento && segmento !== "index" && !/^\(.*\)$/.test(segmento) && !segmento.startsWith("@"))
|
|
597
|
-
.map(normalizarSegmentoRotaConsumer);
|
|
598
|
-
return filtradas.length > 0 ? `/${filtradas.join("/")}`.replace(/\/+/g, "/") : "/";
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
function arquivoEhBridgeNextJsConsumer(relacaoArquivo: string): boolean {
|
|
602
|
-
const relacao = normalizarRelacaoConsumer(relacaoArquivo);
|
|
603
|
-
return /(?:^|\/)(?:src\/)?lib\/(?:sema_consumer_bridge|sema\/.+)\.(?:ts|tsx|js|jsx)$/i.test(relacao);
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
function arquivoEhBridgeReactViteConsumer(relacaoArquivo: string): boolean {
|
|
607
|
-
const relacao = normalizarRelacaoConsumer(relacaoArquivo);
|
|
608
|
-
return /(?:^|\/)(?:src\/)?lib\/(?:sema_consumer_bridge|sema\/.+)\.(?:ts|tsx|js|jsx)$/i.test(relacao);
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
function arquivoEhBridgeAngularConsumer(relacaoArquivo: string): boolean {
|
|
612
|
-
const relacao = normalizarRelacaoConsumer(relacaoArquivo);
|
|
613
|
-
return /(?:^|\/)(?:src\/)?app\/(?:sema_consumer_bridge|sema\/.+)\.(?:ts|js)$/i.test(relacao);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
function arquivoEhSuperficieNextJsConsumer(relacaoArquivo: string): boolean {
|
|
617
|
-
const relacao = normalizarRelacaoConsumer(relacaoArquivo);
|
|
618
|
-
return /(?:^|\/)(?:src\/)?app\/(?:(?!api\/).)*?(?:page|layout|loading|error)\.(?:ts|tsx|js|jsx)$/i.test(relacao);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
function arquivoEhSuperficieReactViteConsumer(relacaoArquivo: string): boolean {
|
|
622
|
-
const relacao = normalizarRelacaoConsumer(relacaoArquivo);
|
|
623
|
-
return /^(?:src\/)?pages\/.+\.(?:ts|tsx|js|jsx)$/i.test(relacao)
|
|
624
|
-
|| /^(?:src\/)?App\.(?:ts|tsx|js|jsx)$/i.test(relacao);
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
function arquivoEhRotasReactViteConsumer(relacaoArquivo: string, codigo?: string): boolean {
|
|
628
|
-
const relacao = normalizarRelacaoConsumer(relacaoArquivo);
|
|
629
|
-
return /(?:^|\/)(?:src\/)?(?:app\/)?(?:router|routes)\.(?:ts|tsx|js|jsx)$/i.test(relacao)
|
|
630
|
-
|| /from\s+["']react-router-dom["']|createBrowserRouter|RouterProvider|useRoutes\s*\(|<Routes\b|<Route\b/.test(codigo ?? "");
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
function arquivoEhRotasAngularConsumer(relacaoArquivo: string): boolean {
|
|
634
|
-
const relacao = normalizarRelacaoConsumer(relacaoArquivo);
|
|
635
|
-
return /(?:^|\/)(?:src\/)?app(?:\/.+)?\/[^/]+\.routes\.(?:ts|js)$/i.test(relacao);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
function arquivoEhRotasAngularConsumerRaiz(relacaoArquivo: string): boolean {
|
|
639
|
-
const relacao = normalizarRelacaoConsumer(relacaoArquivo);
|
|
640
|
-
return /(?:^|\/)(?:src\/)?app\/[^/]+\.routes\.(?:ts|js)$/i.test(relacao);
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
function arquivoEhBridgeFlutterConsumer(relacaoArquivo: string): boolean {
|
|
644
|
-
const relacao = normalizarRelacaoConsumer(relacaoArquivo);
|
|
645
|
-
return /(?:^|\/)(?:lib\/)?(?:sema_consumer_bridge|api\/sema_contract_bridge|sema\/.+)\.dart$/i.test(relacao);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
function arquivoEhSuperficieFlutterConsumer(relacaoArquivo: string): boolean {
|
|
649
|
-
const relacao = normalizarRelacaoConsumer(relacaoArquivo);
|
|
650
|
-
return /(?:^|\/)(?:lib\/)?(?:screens|pages)\/.+\.dart$/i.test(relacao)
|
|
651
|
-
|| /(?:^|\/)(?:lib\/)?main\.dart$/i.test(relacao);
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
function arquivoEhRotasFlutterConsumer(relacaoArquivo: string, codigo?: string): boolean {
|
|
655
|
-
const relacao = normalizarRelacaoConsumer(relacaoArquivo);
|
|
656
|
-
return /(?:^|\/)(?:lib\/)?(?:router|app_router|routes)\.dart$/i.test(relacao)
|
|
657
|
-
|| /MaterialApp(?:\.router)?\s*\(|CupertinoApp(?:\.router)?\s*\(|GoRouter\s*\(/.test(codigo ?? "");
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
function inferirRotaNextJsConsumer(relacaoArquivo: string): RegistroConsumerSurfaceDrift | undefined {
|
|
661
|
-
const relacao = normalizarRelacaoConsumer(relacaoArquivo);
|
|
662
|
-
const segmentos = relacao.split("/");
|
|
663
|
-
const indiceSrcApp = segmentos.findIndex((segmento, indice) => segmento === "src" && segmentos[indice + 1] === "app");
|
|
664
|
-
const indiceApp = segmentos.findIndex((segmento) => segmento === "app");
|
|
665
|
-
const inicioApp = indiceSrcApp >= 0 ? indiceSrcApp + 2 : indiceApp >= 0 ? indiceApp + 1 : -1;
|
|
666
|
-
if (inicioApp < 0) {
|
|
667
|
-
return undefined;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
const arquivoFinal = segmentos.at(-1) ?? "";
|
|
671
|
-
const tipoArquivo = arquivoFinal.match(/^(page|layout|loading|error)\.(?:ts|tsx|js|jsx)$/)?.[1] as RegistroConsumerSurfaceDrift["tipoArquivo"] | undefined;
|
|
672
|
-
if (!tipoArquivo) {
|
|
673
|
-
return undefined;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
const caminhoAteArquivo = segmentos.slice(inicioApp, -1);
|
|
677
|
-
if (caminhoAteArquivo[0] === "api") {
|
|
678
|
-
return undefined;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
return {
|
|
682
|
-
rota: montarRotaConsumer(caminhoAteArquivo),
|
|
683
|
-
arquivo: relacaoArquivo,
|
|
684
|
-
tipoArquivo,
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
function inferirRotaReactViteConsumer(relacaoArquivo: string): RegistroConsumerSurfaceDrift | undefined {
|
|
689
|
-
const relacao = normalizarRelacaoConsumer(relacaoArquivo);
|
|
690
|
-
if (/(?:^|\/)(?:src\/)?App\.(?:ts|tsx|js|jsx)$/i.test(relacao)) {
|
|
691
|
-
return {
|
|
692
|
-
rota: "/",
|
|
693
|
-
arquivo: relacaoArquivo,
|
|
694
|
-
tipoArquivo: "app",
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
const segmentos = relacao.split("/");
|
|
699
|
-
const indiceSrcPages = segmentos.findIndex((segmento, indice) => segmento === "src" && segmentos[indice + 1] === "pages");
|
|
700
|
-
const indicePages = segmentos.findIndex((segmento) => segmento === "pages");
|
|
701
|
-
const inicioPages = indiceSrcPages >= 0 ? indiceSrcPages + 2 : indicePages >= 0 ? indicePages + 1 : -1;
|
|
702
|
-
if (inicioPages < 0) {
|
|
703
|
-
return undefined;
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
const arquivoFinal = segmentos.at(-1) ?? "";
|
|
707
|
-
const nomeBase = arquivoFinal.replace(/\.(?:ts|tsx|js|jsx)$/i, "");
|
|
708
|
-
return {
|
|
709
|
-
rota: montarRotaConsumer([...segmentos.slice(inicioPages, -1), nomeBase]),
|
|
710
|
-
arquivo: relacaoArquivo,
|
|
711
|
-
tipoArquivo: "page",
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
function inferirRotaFlutterConsumer(relacaoArquivo: string): RegistroConsumerSurfaceDrift | undefined {
|
|
716
|
-
const relacao = normalizarRelacaoConsumer(relacaoArquivo);
|
|
717
|
-
if (!arquivoEhSuperficieFlutterConsumer(relacao)) {
|
|
718
|
-
return undefined;
|
|
719
|
-
}
|
|
720
|
-
if (/(?:^|\/)(?:lib\/)?main\.dart$/i.test(relacao)) {
|
|
721
|
-
return {
|
|
722
|
-
rota: "/",
|
|
723
|
-
arquivo: relacaoArquivo,
|
|
724
|
-
tipoArquivo: "app",
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
const segmentos = relacao.split("/");
|
|
729
|
-
const indiceLibScreens = segmentos.findIndex((segmento, indice) => segmento === "lib" && ["screens", "pages"].includes(segmentos[indice + 1] ?? ""));
|
|
730
|
-
const indiceScreens = segmentos.findIndex((segmento) => segmento === "screens" || segmento === "pages");
|
|
731
|
-
const inicio = indiceLibScreens >= 0 ? indiceLibScreens + 2 : indiceScreens >= 0 ? indiceScreens + 1 : -1;
|
|
732
|
-
if (inicio < 0) {
|
|
733
|
-
return undefined;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
const arquivoFinal = segmentos.at(-1) ?? "";
|
|
737
|
-
const nomeBase = arquivoFinal
|
|
738
|
-
.replace(/\.(?:dart)$/i, "")
|
|
739
|
-
.replace(/_(screen|page)$/i, "");
|
|
740
|
-
return {
|
|
741
|
-
rota: montarRotaConsumer([...segmentos.slice(inicio, -1), nomeBase]),
|
|
742
|
-
arquivo: relacaoArquivo,
|
|
743
|
-
tipoArquivo: "screen",
|
|
744
|
-
};
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
interface RotaReactViteConsumerDrift {
|
|
748
|
-
rota: string;
|
|
749
|
-
arquivoRotas: string;
|
|
750
|
-
arquivoComponente?: string;
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
interface RotaFlutterConsumerDrift {
|
|
754
|
-
rota: string;
|
|
755
|
-
arquivoRotas: string;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
interface RotaAngularConsumerDrift {
|
|
759
|
-
rota: string;
|
|
760
|
-
arquivoRotas: string;
|
|
761
|
-
componente?: string;
|
|
762
|
-
arquivoComponente?: string;
|
|
763
|
-
arquivoRotasFilhas?: string;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
function normalizarRotaDeclaradaConsumer(caminhoCru: string, prefixo = "/"): string {
|
|
767
|
-
const partesPrefixo = prefixo.replace(/^\/+|\/+$/g, "").split("/").filter(Boolean);
|
|
768
|
-
const partesCaminho = (caminhoCru ?? "").trim().replace(/^\/+|\/+$/g, "").split("/").filter(Boolean);
|
|
769
|
-
return montarRotaConsumer([...partesPrefixo, ...partesCaminho]);
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
function resolverImportRelativoConsumer(relacaoArquivoBase: string, especificador: string): string | undefined {
|
|
773
|
-
if (!especificador.startsWith(".")) {
|
|
774
|
-
return undefined;
|
|
775
|
-
}
|
|
776
|
-
const baseDir = path.posix.dirname(normalizarRelacaoConsumer(relacaoArquivoBase));
|
|
777
|
-
for (const sufixo of ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.js"]) {
|
|
778
|
-
const candidato = path.posix.normalize(path.posix.join(baseDir, `${especificador}${sufixo}`));
|
|
779
|
-
if (/\.(?:ts|tsx|js|jsx)$/i.test(candidato)) {
|
|
780
|
-
return candidato;
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
return undefined;
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
function extrairImportsTypeScriptConsumer(relacaoArquivo: string, codigo: string): Map<string, string> {
|
|
787
|
-
const imports = new Map<string, string>();
|
|
788
|
-
for (const match of codigo.matchAll(/import\s*\{\s*([^}]+)\s*\}\s*from\s*["']([^"']+)["']/g)) {
|
|
789
|
-
const arquivoImportado = resolverImportRelativoConsumer(relacaoArquivo, match[2]);
|
|
790
|
-
if (!arquivoImportado) {
|
|
791
|
-
continue;
|
|
792
|
-
}
|
|
793
|
-
for (const bruto of match[1].split(",")) {
|
|
794
|
-
const local = bruto.trim().split(/\s+as\s+/i).at(-1)?.trim();
|
|
795
|
-
if (local) {
|
|
796
|
-
imports.set(local, arquivoImportado);
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
for (const match of codigo.matchAll(/import\s+([A-Za-z_]\w*)\s+from\s*["']([^"']+)["']/g)) {
|
|
801
|
-
const arquivoImportado = resolverImportRelativoConsumer(relacaoArquivo, match[2]);
|
|
802
|
-
const local = match[1]?.trim();
|
|
803
|
-
if (arquivoImportado && local) {
|
|
804
|
-
imports.set(local, arquivoImportado);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
return imports;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
function extrairRotasReactViteConsumer(relacaoArquivo: string, codigo: string): RotaReactViteConsumerDrift[] {
|
|
811
|
-
const imports = extrairImportsTypeScriptConsumer(relacaoArquivo, codigo);
|
|
812
|
-
const rotas = new Map<string, RotaReactViteConsumerDrift>();
|
|
813
|
-
const registrar = (caminhoCru: string, componente?: string) => {
|
|
814
|
-
const rota = normalizarRotaDeclaradaConsumer(caminhoCru);
|
|
815
|
-
const chave = `${rota}:${normalizarRelacaoConsumer(relacaoArquivo)}:${componente ?? "router"}`;
|
|
816
|
-
rotas.set(chave, {
|
|
817
|
-
rota,
|
|
818
|
-
arquivoRotas: normalizarRelacaoConsumer(relacaoArquivo),
|
|
819
|
-
arquivoComponente: componente ? imports.get(componente) : undefined,
|
|
820
|
-
});
|
|
821
|
-
};
|
|
822
|
-
|
|
823
|
-
for (const match of codigo.matchAll(/(?:path\s*:\s*["'`]([^"'`]*)["'`]|index\s*:\s*true)[\s\S]{0,260}?(?:element\s*:\s*<\s*([A-Za-z_]\w*)|Component\s*:\s*([A-Za-z_]\w*))/g)) {
|
|
824
|
-
const caminhoCru = match[1] ?? "";
|
|
825
|
-
const componente = match[2] ?? match[3];
|
|
826
|
-
registrar(caminhoCru, componente);
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
for (const match of codigo.matchAll(/<Route\b[^>]*?(?:path=["'`]([^"'`]*)["'`][^>]*?)?(index\b)?[^>]*?(?:element=\{\s*<\s*([A-Za-z_]\w*)|Component=\{\s*([A-Za-z_]\w*))/g)) {
|
|
830
|
-
const caminhoCru = match[2] ? "" : (match[1] ?? "");
|
|
831
|
-
const componente = match[3] ?? match[4];
|
|
832
|
-
registrar(caminhoCru, componente);
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
return [...rotas.values()];
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
function normalizarRotaDeclaradaFlutter(caminhoCru: string): string {
|
|
839
|
-
return montarRotaConsumer((caminhoCru ?? "").trim().replace(/^\/+|\/+$/g, "").split("/").filter(Boolean));
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
function extrairRotasFlutterConsumer(relacaoArquivo: string, codigo: string): RotaFlutterConsumerDrift[] {
|
|
843
|
-
const rotas = new Map<string, RotaFlutterConsumerDrift>();
|
|
844
|
-
const registrar = (caminhoCru: string) => {
|
|
845
|
-
const rota = normalizarRotaDeclaradaFlutter(caminhoCru);
|
|
846
|
-
rotas.set(`${rota}:${normalizarRelacaoConsumer(relacaoArquivo)}`, {
|
|
847
|
-
rota,
|
|
848
|
-
arquivoRotas: normalizarRelacaoConsumer(relacaoArquivo),
|
|
849
|
-
});
|
|
850
|
-
};
|
|
851
|
-
|
|
852
|
-
for (const match of codigo.matchAll(/GoRoute\s*\([\s\S]{0,220}?path\s*:\s*["'`]([^"'`]+)["'`]/g)) {
|
|
853
|
-
registrar(match[1] ?? "");
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
for (const match of codigo.matchAll(/["'`]([^"'`]+)["'`]\s*:\s*\([^)]*\)\s*=>/g)) {
|
|
857
|
-
registrar(match[1] ?? "");
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
if (/home\s*:\s*(?:const\s+)?[A-Za-z_]\w*\(/.test(codigo)) {
|
|
861
|
-
registrar("/");
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
return [...rotas.values()];
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
function extrairRotasAngularConsumerDiretas(relacaoArquivo: string, codigo: string, prefixo = "/"): RotaAngularConsumerDrift[] {
|
|
868
|
-
const imports = extrairImportsTypeScriptConsumer(relacaoArquivo, codigo);
|
|
869
|
-
|
|
870
|
-
const rotas: RotaAngularConsumerDrift[] = [];
|
|
871
|
-
for (const match of codigo.matchAll(/path\s*:\s*["'`]([^"'`]*)["'`][\s\S]{0,320}?component\s*:\s*([A-Za-z_]\w*)/g)) {
|
|
872
|
-
const caminhoCru = (match[1] ?? "").trim();
|
|
873
|
-
const componente = match[2];
|
|
874
|
-
rotas.push({
|
|
875
|
-
rota: normalizarRotaDeclaradaConsumer(caminhoCru, prefixo),
|
|
876
|
-
arquivoRotas: normalizarRelacaoConsumer(relacaoArquivo),
|
|
877
|
-
componente,
|
|
878
|
-
arquivoComponente: imports.get(componente),
|
|
879
|
-
});
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
for (const match of codigo.matchAll(/path\s*:\s*["'`]([^"'`]*)["'`][\s\S]{0,320}?loadComponent\s*:\s*\(\s*\)\s*=>\s*import\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/g)) {
|
|
883
|
-
const caminhoCru = (match[1] ?? "").trim();
|
|
884
|
-
const arquivoComponente = resolverImportRelativoConsumer(relacaoArquivo, match[2] ?? "");
|
|
885
|
-
rotas.push({
|
|
886
|
-
rota: normalizarRotaDeclaradaConsumer(caminhoCru, prefixo),
|
|
887
|
-
arquivoRotas: normalizarRelacaoConsumer(relacaoArquivo),
|
|
888
|
-
arquivoComponente,
|
|
889
|
-
});
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
for (const match of codigo.matchAll(/path\s*:\s*["'`]([^"'`]*)["'`][\s\S]{0,360}?loadChildren\s*:\s*\(\s*\)\s*=>\s*import\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/g)) {
|
|
893
|
-
const caminhoCru = (match[1] ?? "").trim();
|
|
894
|
-
const arquivoRotasFilhas = resolverImportRelativoConsumer(relacaoArquivo, match[2] ?? "");
|
|
895
|
-
rotas.push({
|
|
896
|
-
rota: normalizarRotaDeclaradaConsumer(caminhoCru, prefixo),
|
|
897
|
-
arquivoRotas: normalizarRelacaoConsumer(relacaoArquivo),
|
|
898
|
-
arquivoRotasFilhas,
|
|
899
|
-
});
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
return rotas;
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
async function extrairRotasAngularConsumer(
|
|
906
|
-
diretorioBase: string,
|
|
907
|
-
relacaoArquivo: string,
|
|
908
|
-
prefixo = "/",
|
|
909
|
-
visitados = new Set<string>(),
|
|
910
|
-
): Promise<RotaAngularConsumerDrift[]> {
|
|
911
|
-
const relacaoNormalizada = normalizarRelacaoConsumer(relacaoArquivo);
|
|
912
|
-
if (visitados.has(relacaoNormalizada)) {
|
|
913
|
-
return [];
|
|
914
|
-
}
|
|
915
|
-
visitados.add(relacaoNormalizada);
|
|
916
|
-
|
|
917
|
-
let codigo = "";
|
|
918
|
-
try {
|
|
919
|
-
codigo = await readFile(path.join(diretorioBase, relacaoNormalizada), "utf8");
|
|
920
|
-
} catch {
|
|
921
|
-
return [];
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
const rotas = extrairRotasAngularConsumerDiretas(relacaoNormalizada, codigo, prefixo);
|
|
925
|
-
const filhas: RotaAngularConsumerDrift[] = [];
|
|
926
|
-
for (const rota of rotas) {
|
|
927
|
-
if (!rota.arquivoRotasFilhas) {
|
|
928
|
-
continue;
|
|
929
|
-
}
|
|
930
|
-
filhas.push(...await extrairRotasAngularConsumer(diretorioBase, rota.arquivoRotasFilhas, rota.rota, visitados));
|
|
931
|
-
}
|
|
932
|
-
return [...rotas, ...filhas];
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
function simboloEhBridgeConsumer(caminho: string, arquivo: string): boolean {
|
|
936
|
-
return arquivoEhBridgeNextJsConsumer(arquivo)
|
|
937
|
-
|| arquivoEhBridgeReactViteConsumer(arquivo)
|
|
938
|
-
|| arquivoEhBridgeAngularConsumer(arquivo)
|
|
939
|
-
|| arquivoEhBridgeFlutterConsumer(arquivo)
|
|
940
|
-
|| /(?:^|\.)(?:src\.)?lib\.(?:sema_consumer_bridge|sema\.)/i.test(caminho)
|
|
941
|
-
|| /(?:^|\.)(?:src\.)?app\.(?:sema_consumer_bridge|sema\.)/i.test(caminho)
|
|
942
|
-
|| /(?:^|\.)(?:lib\.)?(?:sema_consumer_bridge|api\.sema_contract_bridge|sema\.)/i.test(caminho);
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
function inferirConsumerFrameworkPrincipal(
|
|
946
|
-
fontesLegado: FonteLegado[],
|
|
947
|
-
consumerSurfaces: RegistroConsumerSurfaceDrift[],
|
|
948
|
-
consumerBridges: RegistroConsumerBridgeDrift[],
|
|
949
|
-
): ConsumerFramework | null {
|
|
950
|
-
const arquivos = [
|
|
951
|
-
...consumerSurfaces.map((item) => item.arquivo),
|
|
952
|
-
...consumerBridges.map((item) => item.arquivo),
|
|
953
|
-
].map(normalizarRelacaoConsumer);
|
|
954
|
-
if (arquivos.some((arquivo) => /(?:^|\/)(?:src\/)?app\/(?:(?!api\/).)*?(?:page|layout|loading|error)\.(?:ts|tsx|js|jsx)$/i.test(arquivo))) {
|
|
955
|
-
return "nextjs-consumer";
|
|
956
|
-
}
|
|
957
|
-
if (arquivos.some((arquivo) =>
|
|
958
|
-
/^(?:src\/)?pages\/.+\.(?:ts|tsx|js|jsx)$/i.test(arquivo)
|
|
959
|
-
|| /^(?:src\/)?App\.(?:ts|tsx|js|jsx)$/i.test(arquivo)
|
|
960
|
-
|| /(?:^|\/)(?:src\/)?(?:app\/)?(?:router|routes)\.(?:ts|tsx|js|jsx)$/i.test(arquivo))) {
|
|
961
|
-
return "react-vite-consumer";
|
|
962
|
-
}
|
|
963
|
-
if (arquivos.some((arquivo) => /(?:^|\/)(?:src\/)?app\/.+\.component\.(?:ts|js)$/i.test(arquivo) || arquivoEhRotasAngularConsumer(arquivo))) {
|
|
964
|
-
return "angular-consumer";
|
|
965
|
-
}
|
|
966
|
-
if (arquivos.some((arquivo) =>
|
|
967
|
-
/(?:^|\/)(?:lib\/)?(?:screens|pages)\/.+\.dart$/i.test(arquivo)
|
|
968
|
-
|| /(?:^|\/)(?:lib\/)?(?:router|app_router|routes|main)\.dart$/i.test(arquivo))) {
|
|
969
|
-
return "flutter-consumer";
|
|
970
|
-
}
|
|
971
|
-
for (const framework of ["nextjs-consumer", "react-vite-consumer", "angular-consumer", "flutter-consumer"] as const) {
|
|
972
|
-
if (fontesLegado.includes(framework)) {
|
|
973
|
-
return framework;
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
return null;
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
function extrairColecoesFirebase(arquivo: string, codigo: string): RecursoResolvido[] {
|
|
980
|
-
const recursos = new Map<string, RecursoResolvido>();
|
|
981
|
-
const registrar = (nome: string) => {
|
|
982
|
-
if (!nome) {
|
|
983
|
-
return;
|
|
984
|
-
}
|
|
985
|
-
recursos.set(`${nome}:${arquivo}`, {
|
|
986
|
-
origem: "firebase",
|
|
987
|
-
nome,
|
|
988
|
-
arquivo,
|
|
989
|
-
tipo: "colecao",
|
|
990
|
-
});
|
|
991
|
-
};
|
|
992
|
-
|
|
993
|
-
for (const match of codigo.matchAll(/\b(?:export\s+)?const\s+\w*COLLECTIONS?\w*\s*=\s*\{([\s\S]*?)\n\}/g)) {
|
|
994
|
-
const corpo = match[1] ?? "";
|
|
995
|
-
for (const valor of corpo.matchAll(/:\s*["'`]([^"'`]+)["'`]/g)) {
|
|
996
|
-
registrar(valor[1]!);
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
for (const match of codigo.matchAll(/\b(?:db\.)?collection\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/g)) {
|
|
1001
|
-
registrar(match[1]!);
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
for (const match of codigo.matchAll(/\bdoc\s*\(\s*[^,]+,\s*["'`]([^"'`]+)["'`]/g)) {
|
|
1005
|
-
registrar(match[1]!);
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
return [...recursos.values()];
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
async function indexarTypeScript(diretorios: string[]): Promise<{
|
|
1012
|
-
simbolos: SimboloResolvido[];
|
|
1013
|
-
rotas: RotaResolvida[];
|
|
1014
|
-
recursos: RecursoResolvido[];
|
|
1015
|
-
consumerSurfaces: RegistroConsumerSurfaceDrift[];
|
|
1016
|
-
}> {
|
|
1017
|
-
const simbolos = new Map<string, SimboloResolvido>();
|
|
1018
|
-
const rotas: RotaResolvida[] = [];
|
|
1019
|
-
const recursos = new Map<string, RecursoResolvido>();
|
|
1020
|
-
const consumerSurfaces = new Map<string, RegistroConsumerSurfaceDrift>();
|
|
1021
|
-
|
|
1022
|
-
for (const diretorio of diretorios) {
|
|
1023
|
-
const arquivos = (await listarArquivosRecursivos(diretorio, [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]))
|
|
1024
|
-
.filter((arquivo) =>
|
|
1025
|
-
!arquivo.endsWith(".d.ts")
|
|
1026
|
-
&& !arquivo.endsWith(".spec.ts")
|
|
1027
|
-
&& !arquivo.endsWith(".test.ts"),
|
|
1028
|
-
);
|
|
1029
|
-
const arquivosRotasAngular = arquivos.filter((arquivo) => arquivoEhRotasAngularConsumer(path.relative(diretorio, arquivo)));
|
|
1030
|
-
const arquivosRotasAngularRaiz = new Set(
|
|
1031
|
-
arquivosRotasAngular
|
|
1032
|
-
.filter((arquivo) => arquivoEhRotasAngularConsumerRaiz(path.relative(diretorio, arquivo)))
|
|
1033
|
-
.map((arquivo) => path.resolve(arquivo)),
|
|
1034
|
-
);
|
|
1035
|
-
const usarApenasRotasAngularRaiz = arquivosRotasAngularRaiz.size > 0;
|
|
1036
|
-
|
|
1037
|
-
for (const arquivo of arquivos) {
|
|
1038
|
-
const codigo = await readFile(arquivo, "utf8");
|
|
1039
|
-
const scriptKind = arquivo.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
1040
|
-
const sourceFile = ts.createSourceFile(arquivo, codigo, ts.ScriptTarget.Latest, true, scriptKind);
|
|
1041
|
-
const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
|
|
1042
|
-
const relacao = path.relative(diretorio, arquivo);
|
|
1043
|
-
|
|
1044
|
-
for (const recurso of extrairColecoesFirebase(arquivo, codigo)) {
|
|
1045
|
-
recursos.set(`${recurso.nome}:${recurso.arquivo}:${recurso.tipo}`, recurso);
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
for (const rota of extrairRotasTypeScriptHttp(sourceFile, relacao)) {
|
|
1049
|
-
rotas.push({
|
|
1050
|
-
origem: rota.origem,
|
|
1051
|
-
metodo: rota.metodo,
|
|
1052
|
-
caminho: rota.caminho,
|
|
1053
|
-
arquivo,
|
|
1054
|
-
simbolo: rota.simbolo,
|
|
1055
|
-
});
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
const superficieNextJs = arquivoEhSuperficieNextJsConsumer(relacao)
|
|
1059
|
-
? inferirRotaNextJsConsumer(relacao)
|
|
1060
|
-
: undefined;
|
|
1061
|
-
if (superficieNextJs) {
|
|
1062
|
-
consumerSurfaces.set(`${superficieNextJs.rota}:${arquivo}:${superficieNextJs.tipoArquivo}`, {
|
|
1063
|
-
rota: superficieNextJs.rota,
|
|
1064
|
-
arquivo,
|
|
1065
|
-
tipoArquivo: superficieNextJs.tipoArquivo,
|
|
1066
|
-
});
|
|
1067
|
-
rotas.push({
|
|
1068
|
-
origem: "nextjs-consumer",
|
|
1069
|
-
metodo: "VIEW",
|
|
1070
|
-
caminho: superficieNextJs.rota,
|
|
1071
|
-
arquivo,
|
|
1072
|
-
simbolo: superficieNextJs.tipoArquivo,
|
|
1073
|
-
});
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
const superficieReact = arquivoEhSuperficieReactViteConsumer(relacao)
|
|
1077
|
-
? inferirRotaReactViteConsumer(relacao)
|
|
1078
|
-
: undefined;
|
|
1079
|
-
if (superficieReact) {
|
|
1080
|
-
consumerSurfaces.set(`${superficieReact.rota}:${arquivo}:${superficieReact.tipoArquivo}`, {
|
|
1081
|
-
rota: superficieReact.rota,
|
|
1082
|
-
arquivo,
|
|
1083
|
-
tipoArquivo: superficieReact.tipoArquivo,
|
|
1084
|
-
});
|
|
1085
|
-
rotas.push({
|
|
1086
|
-
origem: "react-vite-consumer",
|
|
1087
|
-
metodo: "VIEW",
|
|
1088
|
-
caminho: superficieReact.rota,
|
|
1089
|
-
arquivo,
|
|
1090
|
-
simbolo: superficieReact.tipoArquivo,
|
|
1091
|
-
});
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
if (arquivoEhRotasReactViteConsumer(relacao, codigo)) {
|
|
1095
|
-
for (const rotaReact of extrairRotasReactViteConsumer(relacao, codigo)) {
|
|
1096
|
-
consumerSurfaces.set(`${rotaReact.rota}:${arquivo}:router`, {
|
|
1097
|
-
rota: rotaReact.rota,
|
|
1098
|
-
arquivo,
|
|
1099
|
-
tipoArquivo: "router",
|
|
1100
|
-
});
|
|
1101
|
-
rotas.push({
|
|
1102
|
-
origem: "react-vite-consumer",
|
|
1103
|
-
metodo: "VIEW",
|
|
1104
|
-
caminho: rotaReact.rota,
|
|
1105
|
-
arquivo,
|
|
1106
|
-
simbolo: "router",
|
|
1107
|
-
});
|
|
1108
|
-
if (rotaReact.arquivoComponente) {
|
|
1109
|
-
const arquivoComponente = path.join(diretorio, rotaReact.arquivoComponente);
|
|
1110
|
-
consumerSurfaces.set(`${rotaReact.rota}:${arquivoComponente}:page`, {
|
|
1111
|
-
rota: rotaReact.rota,
|
|
1112
|
-
arquivo: arquivoComponente,
|
|
1113
|
-
tipoArquivo: "page",
|
|
1114
|
-
});
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
if (arquivoEhRotasAngularConsumer(relacao) && (!usarApenasRotasAngularRaiz || arquivosRotasAngularRaiz.has(path.resolve(arquivo)))) {
|
|
1120
|
-
for (const rotaAngular of await extrairRotasAngularConsumer(diretorio, relacao)) {
|
|
1121
|
-
const arquivoRotasAngular = path.join(diretorio, rotaAngular.arquivoRotas);
|
|
1122
|
-
consumerSurfaces.set(`${rotaAngular.rota}:${arquivoRotasAngular}:routes`, {
|
|
1123
|
-
rota: rotaAngular.rota,
|
|
1124
|
-
arquivo: arquivoRotasAngular,
|
|
1125
|
-
tipoArquivo: "routes",
|
|
1126
|
-
});
|
|
1127
|
-
rotas.push({
|
|
1128
|
-
origem: "angular-consumer",
|
|
1129
|
-
metodo: "VIEW",
|
|
1130
|
-
caminho: rotaAngular.rota,
|
|
1131
|
-
arquivo: arquivoRotasAngular,
|
|
1132
|
-
simbolo: rotaAngular.componente ?? "routes",
|
|
1133
|
-
});
|
|
1134
|
-
if (rotaAngular.arquivoComponente) {
|
|
1135
|
-
const arquivoComponente = path.join(diretorio, rotaAngular.arquivoComponente);
|
|
1136
|
-
consumerSurfaces.set(`${rotaAngular.rota}:${arquivoComponente}:component`, {
|
|
1137
|
-
rota: rotaAngular.rota,
|
|
1138
|
-
arquivo: arquivoComponente,
|
|
1139
|
-
tipoArquivo: "component",
|
|
1140
|
-
});
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
for (const node of sourceFile.statements) {
|
|
1146
|
-
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
1147
|
-
registrarSimboloTypeScript(simbolos, basesSimbolicas, arquivo, node.name.text);
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
if (ts.isVariableStatement(node)) {
|
|
1151
|
-
for (const declaracao of node.declarationList.declarations) {
|
|
1152
|
-
if (!ts.isIdentifier(declaracao.name) || !declaracao.initializer) {
|
|
1153
|
-
continue;
|
|
1154
|
-
}
|
|
1155
|
-
if (ts.isArrowFunction(declaracao.initializer) || ts.isFunctionExpression(declaracao.initializer)) {
|
|
1156
|
-
registrarSimboloTypeScript(simbolos, basesSimbolicas, arquivo, declaracao.name.text);
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
if (!ts.isClassDeclaration(node) || !node.name) {
|
|
1162
|
-
continue;
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
const controllerDecorator = lerDecorator(node, ["Controller"]);
|
|
1166
|
-
const basePath = extrairTextoLiteral(controllerDecorator?.argumentos[0]);
|
|
1167
|
-
|
|
1168
|
-
for (const member of node.members) {
|
|
1169
|
-
if (!ts.isMethodDeclaration(member) || !member.name) {
|
|
1170
|
-
continue;
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
const nomeMetodo = member.name.getText(sourceFile);
|
|
1174
|
-
if (nomeMetodo === "constructor") {
|
|
1175
|
-
continue;
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
registrarSimboloTypeScript(simbolos, basesSimbolicas, arquivo, nomeMetodo, node.name.text);
|
|
1179
|
-
const metodoEhInterno = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.PrivateKeyword || m.kind === ts.SyntaxKind.ProtectedKeyword);
|
|
1180
|
-
for (const baseSimbolica of basesSimbolicas) {
|
|
1181
|
-
const caminhoMetodoDireto = `${baseSimbolica}.${nomeMetodo}`;
|
|
1182
|
-
if (!simbolos.has(caminhoMetodoDireto)) {
|
|
1183
|
-
simbolos.set(caminhoMetodoDireto, { origem: "ts", caminho: caminhoMetodoDireto, arquivo, simbolo: nomeMetodo });
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
if (controllerDecorator && !metodoEhInterno) {
|
|
1188
|
-
const httpDecorator = lerDecorator(member, ["Get", "Post", "Put", "Patch", "Delete"]);
|
|
1189
|
-
if (httpDecorator) {
|
|
1190
|
-
rotas.push({
|
|
1191
|
-
origem: "nestjs",
|
|
1192
|
-
metodo: httpDecorator.nome.toUpperCase(),
|
|
1193
|
-
caminho: juntarCaminhoHttp(basePath, extrairTextoLiteral(httpDecorator.argumentos[0])),
|
|
1194
|
-
arquivo,
|
|
1195
|
-
simbolo: `${node.name.text}.${nomeMetodo}`,
|
|
1196
|
-
});
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
return {
|
|
1205
|
-
simbolos: [...simbolos.values()],
|
|
1206
|
-
rotas,
|
|
1207
|
-
recursos: [...recursos.values()],
|
|
1208
|
-
consumerSurfaces: [...consumerSurfaces.values()].sort((a, b) =>
|
|
1209
|
-
a.rota.localeCompare(b.rota, "pt-BR")
|
|
1210
|
-
|| a.tipoArquivo.localeCompare(b.tipoArquivo, "pt-BR")
|
|
1211
|
-
|| a.arquivo.localeCompare(b.arquivo, "pt-BR")),
|
|
1212
|
-
};
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
interface BlocoPython {
|
|
1216
|
-
tipo: "class" | "def";
|
|
1217
|
-
nome: string;
|
|
1218
|
-
indentacao: number;
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
function registrarSimboloPython(
|
|
1222
|
-
simbolos: Map<string, SimboloResolvido>,
|
|
1223
|
-
basesSimbolicas: string[],
|
|
1224
|
-
arquivo: string,
|
|
1225
|
-
nome: string,
|
|
1226
|
-
nomeClasse?: string,
|
|
1227
|
-
): void {
|
|
1228
|
-
for (const baseSimbolica of basesSimbolicas) {
|
|
1229
|
-
const caminho = nomeClasse
|
|
1230
|
-
? `${baseSimbolica}.${nomeClasse}.${nome}`
|
|
1231
|
-
: `${baseSimbolica}.${nome}`;
|
|
1232
|
-
simbolos.set(caminho, {
|
|
1233
|
-
origem: "py",
|
|
1234
|
-
caminho,
|
|
1235
|
-
arquivo,
|
|
1236
|
-
simbolo: nomeClasse ? `${nomeClasse}.${nome}` : nome,
|
|
1237
|
-
});
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
function registrarRotasPython(
|
|
1242
|
-
rotas: RotaResolvida[],
|
|
1243
|
-
decoratorsPendentes: string[],
|
|
1244
|
-
prefixo: string | undefined,
|
|
1245
|
-
arquivo: string,
|
|
1246
|
-
nomeFuncao: string,
|
|
1247
|
-
): void {
|
|
1248
|
-
for (const decorator of decoratorsPendentes) {
|
|
1249
|
-
const match = decorator.match(/^@(router|app)\.(get|post|put|patch|delete)\((.*)\)\s*$/);
|
|
1250
|
-
if (!match) {
|
|
1251
|
-
continue;
|
|
1252
|
-
}
|
|
1253
|
-
const metodo = match[2]!.toUpperCase();
|
|
1254
|
-
const sufixo = match[3]?.match(/["']([^"']+)["']/)?.[1];
|
|
1255
|
-
rotas.push({
|
|
1256
|
-
origem: "fastapi",
|
|
1257
|
-
metodo,
|
|
1258
|
-
caminho: juntarCaminhoHttp(prefixo, sufixo),
|
|
1259
|
-
arquivo,
|
|
1260
|
-
simbolo: nomeFuncao,
|
|
1261
|
-
});
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
async function indexarPython(diretorios: string[]): Promise<{ simbolos: SimboloResolvido[]; rotas: RotaResolvida[] }> {
|
|
1266
|
-
const simbolos = new Map<string, SimboloResolvido>();
|
|
1267
|
-
const rotas: RotaResolvida[] = [];
|
|
1268
|
-
|
|
1269
|
-
for (const diretorio of diretorios) {
|
|
1270
|
-
const arquivos = (await listarArquivosRecursivos(diretorio, [".py"]))
|
|
1271
|
-
.filter((arquivo) => !arquivo.endsWith("__init__.py") && !/tests?[\\/]/i.test(arquivo));
|
|
1272
|
-
|
|
1273
|
-
for (const arquivo of arquivos) {
|
|
1274
|
-
const texto = await readFile(arquivo, "utf8");
|
|
1275
|
-
const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
|
|
1276
|
-
const prefixo = texto.match(/APIRouter\s*\(\s*prefix\s*=\s*["']([^"']+)["']/)?.[1];
|
|
1277
|
-
for (const rota of extrairRotasFlaskDecoradas(texto)) {
|
|
1278
|
-
rotas.push({
|
|
1279
|
-
origem: "flask",
|
|
1280
|
-
metodo: rota.metodo,
|
|
1281
|
-
caminho: rota.caminho,
|
|
1282
|
-
arquivo,
|
|
1283
|
-
simbolo: rota.nomeFuncao,
|
|
1284
|
-
});
|
|
1285
|
-
}
|
|
1286
|
-
const blocos: BlocoPython[] = [];
|
|
1287
|
-
let decoratorsPendentes: string[] = [];
|
|
1288
|
-
|
|
1289
|
-
for (const linha of texto.split(/\r?\n/)) {
|
|
1290
|
-
const trim = linha.trim();
|
|
1291
|
-
if (trim === "" || trim.startsWith("#")) {
|
|
1292
|
-
decoratorsPendentes = [];
|
|
1293
|
-
continue;
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
const indentacao = contarIndentacaoPython(linha);
|
|
1297
|
-
while (blocos.length > 0 && indentacao <= blocos[blocos.length - 1]!.indentacao) {
|
|
1298
|
-
blocos.pop();
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
if (trim.startsWith("@")) {
|
|
1302
|
-
decoratorsPendentes.push(trim);
|
|
1303
|
-
continue;
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
const classe = trim.match(/^class\s+([A-Za-z_]\w*)(?:\([^)]*\))?:\s*(?:#.*)?$/);
|
|
1307
|
-
if (classe) {
|
|
1308
|
-
blocos.push({ tipo: "class", nome: classe[1]!, indentacao });
|
|
1309
|
-
decoratorsPendentes = [];
|
|
1310
|
-
continue;
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
const definicao = trim.match(/^(?:async\s+def|def)\s+([A-Za-z_]\w*)\s*\(/);
|
|
1314
|
-
if (definicao) {
|
|
1315
|
-
const nomeFuncao = definicao[1]!;
|
|
1316
|
-
const existeDefPai = blocos.some((bloco) => bloco.tipo === "def");
|
|
1317
|
-
const classeAtual = [...blocos].reverse().find((bloco) => bloco.tipo === "class");
|
|
1318
|
-
|
|
1319
|
-
if (!existeDefPai && classeAtual) {
|
|
1320
|
-
registrarSimboloPython(simbolos, basesSimbolicas, arquivo, nomeFuncao, classeAtual.nome);
|
|
1321
|
-
} else if (!existeDefPai) {
|
|
1322
|
-
registrarSimboloPython(simbolos, basesSimbolicas, arquivo, nomeFuncao);
|
|
1323
|
-
registrarRotasPython(rotas, decoratorsPendentes, prefixo, arquivo, nomeFuncao);
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
blocos.push({ tipo: "def", nome: nomeFuncao, indentacao });
|
|
1327
|
-
decoratorsPendentes = [];
|
|
1328
|
-
continue;
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
decoratorsPendentes = [];
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
return { simbolos: [...simbolos.values()], rotas };
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
async function indexarDart(diretorios: string[]): Promise<{
|
|
1340
|
-
simbolos: SimboloResolvido[];
|
|
1341
|
-
rotas: RotaResolvida[];
|
|
1342
|
-
consumerSurfaces: RegistroConsumerSurfaceDrift[];
|
|
1343
|
-
}> {
|
|
1344
|
-
const simbolos = new Map<string, SimboloResolvido>();
|
|
1345
|
-
const rotas: RotaResolvida[] = [];
|
|
1346
|
-
const consumerSurfaces = new Map<string, RegistroConsumerSurfaceDrift>();
|
|
1347
|
-
|
|
1348
|
-
for (const diretorio of diretorios) {
|
|
1349
|
-
const arquivos = (await listarArquivosRecursivos(diretorio, [".dart"]))
|
|
1350
|
-
.filter((arquivo) => !arquivo.endsWith(".g.dart") && !arquivo.endsWith(".freezed.dart"));
|
|
1351
|
-
|
|
1352
|
-
for (const arquivo of arquivos) {
|
|
1353
|
-
const texto = await readFile(arquivo, "utf8");
|
|
1354
|
-
const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
|
|
1355
|
-
const relacao = path.relative(diretorio, arquivo);
|
|
1356
|
-
|
|
1357
|
-
for (const match of texto.matchAll(/(?:Future<[^\n]+>|[\w?<>.,\s]+)\s+(\w+)\(([^)]*)\)\s*(?:async\s*)?\{/g)) {
|
|
1358
|
-
const nome = match[1]!;
|
|
1359
|
-
if (["build", "toString"].includes(nome)) {
|
|
1360
|
-
continue;
|
|
1361
|
-
}
|
|
1362
|
-
for (const baseSimbolica of basesSimbolicas) {
|
|
1363
|
-
const caminho = `${baseSimbolica}.${nome}`;
|
|
1364
|
-
simbolos.set(caminho, { origem: "dart", caminho, arquivo, simbolo: nome });
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
const superficieFlutter = inferirRotaFlutterConsumer(relacao);
|
|
1369
|
-
if (superficieFlutter) {
|
|
1370
|
-
consumerSurfaces.set(`${superficieFlutter.rota}:${arquivo}:${superficieFlutter.tipoArquivo}`, {
|
|
1371
|
-
rota: superficieFlutter.rota,
|
|
1372
|
-
arquivo,
|
|
1373
|
-
tipoArquivo: superficieFlutter.tipoArquivo,
|
|
1374
|
-
});
|
|
1375
|
-
rotas.push({
|
|
1376
|
-
origem: "flutter-consumer",
|
|
1377
|
-
metodo: "VIEW",
|
|
1378
|
-
caminho: superficieFlutter.rota,
|
|
1379
|
-
arquivo,
|
|
1380
|
-
simbolo: superficieFlutter.tipoArquivo,
|
|
1381
|
-
});
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
if (arquivoEhRotasFlutterConsumer(relacao, texto)) {
|
|
1385
|
-
for (const rotaFlutter of extrairRotasFlutterConsumer(relacao, texto)) {
|
|
1386
|
-
consumerSurfaces.set(`${rotaFlutter.rota}:${arquivo}:router`, {
|
|
1387
|
-
rota: rotaFlutter.rota,
|
|
1388
|
-
arquivo,
|
|
1389
|
-
tipoArquivo: "router",
|
|
1390
|
-
});
|
|
1391
|
-
rotas.push({
|
|
1392
|
-
origem: "flutter-consumer",
|
|
1393
|
-
metodo: "VIEW",
|
|
1394
|
-
caminho: rotaFlutter.rota,
|
|
1395
|
-
arquivo,
|
|
1396
|
-
simbolo: "router",
|
|
1397
|
-
});
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
return {
|
|
1404
|
-
simbolos: [...simbolos.values()],
|
|
1405
|
-
rotas,
|
|
1406
|
-
consumerSurfaces: [...consumerSurfaces.values()].sort((a, b) =>
|
|
1407
|
-
a.rota.localeCompare(b.rota, "pt-BR")
|
|
1408
|
-
|| a.tipoArquivo.localeCompare(b.tipoArquivo, "pt-BR")
|
|
1409
|
-
|| a.arquivo.localeCompare(b.arquivo, "pt-BR")),
|
|
1410
|
-
};
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
function registrarSimboloGenerico(
|
|
1414
|
-
simbolos: Map<string, SimboloResolvido>,
|
|
1415
|
-
origem: SimboloResolvido["origem"],
|
|
1416
|
-
basesSimbolicas: string[],
|
|
1417
|
-
arquivo: string,
|
|
1418
|
-
simbolo: string,
|
|
1419
|
-
): void {
|
|
1420
|
-
for (const baseSimbolica of basesSimbolicas) {
|
|
1421
|
-
const caminho = `${baseSimbolica}.${simbolo}`;
|
|
1422
|
-
simbolos.set(caminho, {
|
|
1423
|
-
origem,
|
|
1424
|
-
caminho,
|
|
1425
|
-
arquivo,
|
|
1426
|
-
simbolo,
|
|
1427
|
-
});
|
|
1428
|
-
|
|
1429
|
-
const ultimo = simbolo.split(".").at(-1);
|
|
1430
|
-
if (ultimo) {
|
|
1431
|
-
const caminhoDireto = `${baseSimbolica}.${ultimo}`;
|
|
1432
|
-
if (!simbolos.has(caminhoDireto)) {
|
|
1433
|
-
simbolos.set(caminhoDireto, {
|
|
1434
|
-
origem,
|
|
1435
|
-
caminho: caminhoDireto,
|
|
1436
|
-
arquivo,
|
|
1437
|
-
simbolo: ultimo,
|
|
1438
|
-
});
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
async function indexarDotnet(diretorios: string[]): Promise<{ simbolos: SimboloResolvido[]; rotas: RotaResolvida[] }> {
|
|
1445
|
-
const simbolos = new Map<string, SimboloResolvido>();
|
|
1446
|
-
const rotas: RotaResolvida[] = [];
|
|
1447
|
-
|
|
1448
|
-
for (const diretorio of diretorios) {
|
|
1449
|
-
const arquivos = (await listarArquivosRecursivos(diretorio, [".cs"]))
|
|
1450
|
-
.filter((arquivo) => !/(^|[\\/])(bin|obj|Test[s]?)([\\/]|$)/i.test(arquivo));
|
|
1451
|
-
|
|
1452
|
-
for (const arquivo of arquivos) {
|
|
1453
|
-
const codigo = await readFile(arquivo, "utf8");
|
|
1454
|
-
const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
|
|
1455
|
-
for (const simbolo of extrairSimbolosDotnet(codigo)) {
|
|
1456
|
-
registrarSimboloGenerico(simbolos, "cs", basesSimbolicas, arquivo, simbolo.simbolo);
|
|
1457
|
-
}
|
|
1458
|
-
for (const rota of extrairRotasDotnet(codigo)) {
|
|
1459
|
-
rotas.push({
|
|
1460
|
-
origem: "dotnet",
|
|
1461
|
-
metodo: rota.metodo,
|
|
1462
|
-
caminho: rota.caminho,
|
|
1463
|
-
arquivo,
|
|
1464
|
-
simbolo: rota.simbolo,
|
|
1465
|
-
});
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
return { simbolos: [...simbolos.values()], rotas };
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
async function indexarJava(diretorios: string[]): Promise<{ simbolos: SimboloResolvido[]; rotas: RotaResolvida[] }> {
|
|
1474
|
-
const simbolos = new Map<string, SimboloResolvido>();
|
|
1475
|
-
const rotas: RotaResolvida[] = [];
|
|
1476
|
-
|
|
1477
|
-
for (const diretorio of diretorios) {
|
|
1478
|
-
const arquivos = (await listarArquivosRecursivos(diretorio, [".java"]))
|
|
1479
|
-
.filter((arquivo) => !/(^|[\\/])(target|build|out|Test[s]?)([\\/]|$)/i.test(arquivo));
|
|
1480
|
-
|
|
1481
|
-
for (const arquivo of arquivos) {
|
|
1482
|
-
const codigo = await readFile(arquivo, "utf8");
|
|
1483
|
-
const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
|
|
1484
|
-
for (const simbolo of extrairSimbolosJava(codigo)) {
|
|
1485
|
-
registrarSimboloGenerico(simbolos, "java", basesSimbolicas, arquivo, simbolo.simbolo);
|
|
1486
|
-
}
|
|
1487
|
-
for (const rota of extrairRotasJava(codigo)) {
|
|
1488
|
-
rotas.push({
|
|
1489
|
-
origem: "java",
|
|
1490
|
-
metodo: rota.metodo,
|
|
1491
|
-
caminho: rota.caminho,
|
|
1492
|
-
arquivo,
|
|
1493
|
-
simbolo: rota.simbolo,
|
|
1494
|
-
});
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
return { simbolos: [...simbolos.values()], rotas };
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
async function indexarGo(diretorios: string[]): Promise<{ simbolos: SimboloResolvido[]; rotas: RotaResolvida[] }> {
|
|
1503
|
-
const simbolos = new Map<string, SimboloResolvido>();
|
|
1504
|
-
const rotas: RotaResolvida[] = [];
|
|
1505
|
-
|
|
1506
|
-
for (const diretorio of diretorios) {
|
|
1507
|
-
const arquivos = await listarArquivosRecursivos(diretorio, [".go"]);
|
|
1508
|
-
|
|
1509
|
-
for (const arquivo of arquivos) {
|
|
1510
|
-
const codigo = await readFile(arquivo, "utf8");
|
|
1511
|
-
const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
|
|
1512
|
-
for (const simbolo of extrairSimbolosGo(codigo)) {
|
|
1513
|
-
registrarSimboloGenerico(simbolos, "go", basesSimbolicas, arquivo, simbolo.simbolo);
|
|
1514
|
-
}
|
|
1515
|
-
for (const rota of extrairRotasGo(codigo)) {
|
|
1516
|
-
rotas.push({
|
|
1517
|
-
origem: "go",
|
|
1518
|
-
metodo: rota.metodo,
|
|
1519
|
-
caminho: rota.caminho,
|
|
1520
|
-
arquivo,
|
|
1521
|
-
simbolo: rota.simbolo,
|
|
1522
|
-
});
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
|
|
1527
|
-
return { simbolos: [...simbolos.values()], rotas };
|
|
1528
|
-
}
|
|
1529
|
-
|
|
1530
|
-
async function indexarRust(diretorios: string[]): Promise<{ simbolos: SimboloResolvido[]; rotas: RotaResolvida[] }> {
|
|
1531
|
-
const simbolos = new Map<string, SimboloResolvido>();
|
|
1532
|
-
const rotas: RotaResolvida[] = [];
|
|
1533
|
-
|
|
1534
|
-
for (const diretorio of diretorios) {
|
|
1535
|
-
const arquivos = await listarArquivosRecursivos(diretorio, [".rs"]);
|
|
1536
|
-
|
|
1537
|
-
for (const arquivo of arquivos) {
|
|
1538
|
-
const codigo = await readFile(arquivo, "utf8");
|
|
1539
|
-
const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
|
|
1540
|
-
for (const simbolo of extrairSimbolosRust(codigo)) {
|
|
1541
|
-
registrarSimboloGenerico(simbolos, "rust", basesSimbolicas, arquivo, simbolo.simbolo);
|
|
1542
|
-
}
|
|
1543
|
-
for (const rota of extrairRotasRust(codigo)) {
|
|
1544
|
-
rotas.push({
|
|
1545
|
-
origem: "rust",
|
|
1546
|
-
metodo: rota.metodo,
|
|
1547
|
-
caminho: rota.caminho,
|
|
1548
|
-
arquivo,
|
|
1549
|
-
simbolo: rota.simbolo,
|
|
1550
|
-
});
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
return { simbolos: [...simbolos.values()], rotas };
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
async function indexarCpp(diretorios: string[]): Promise<SimboloResolvido[]> {
|
|
1559
|
-
const simbolos = new Map<string, SimboloResolvido>();
|
|
1560
|
-
|
|
1561
|
-
for (const diretorio of diretorios) {
|
|
1562
|
-
const arquivos = (await listarArquivosRecursivos(diretorio, [".cpp", ".cc", ".cxx", ".hpp", ".h"]))
|
|
1563
|
-
.filter((arquivo) => !/(^|[\\/])(windows|linux|macos|runner|flutter|ephemeral|build|vendor)([\\/]|$)/i.test(arquivo));
|
|
1564
|
-
|
|
1565
|
-
for (const arquivo of arquivos) {
|
|
1566
|
-
const codigo = await readFile(arquivo, "utf8");
|
|
1567
|
-
const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
|
|
1568
|
-
for (const simbolo of extrairSimbolosCpp(codigo)) {
|
|
1569
|
-
registrarSimboloGenerico(simbolos, "cpp", basesSimbolicas, arquivo, simbolo.simbolo);
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
return [...simbolos.values()];
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
function normalizarCaminhoRota(caminho?: string): string {
|
|
1578
|
-
if (!caminho) {
|
|
1579
|
-
return "/";
|
|
1580
|
-
}
|
|
1581
|
-
const limpo = normalizarCaminhoFlask(caminho.trim().replace(/\s*\/\s*/g, "/"));
|
|
1582
|
-
const comBarra = limpo.startsWith("/") ? limpo : `/${limpo}`;
|
|
1583
|
-
const normalizado = comBarra.replace(/\/+/g, "/");
|
|
1584
|
-
return normalizado.endsWith("/") && normalizado !== "/" ? normalizado.slice(0, -1) : normalizado;
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
function extrairFontesHttpTypeScript(fontesLegado: FonteLegado[]): Array<"nestjs" | "nextjs" | "firebase"> {
|
|
1588
|
-
return fontesLegado.filter((fonte): fonte is "nestjs" | "nextjs" | "firebase" =>
|
|
1589
|
-
fonte === "nestjs" || fonte === "nextjs" || fonte === "firebase");
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
function extrairFontesHttpBackend(fontesLegado: FonteLegado[]): Array<"dotnet" | "java" | "go" | "rust"> {
|
|
1593
|
-
return fontesLegado.filter((fonte): fonte is "dotnet" | "java" | "go" | "rust" =>
|
|
1594
|
-
fonte === "dotnet" || fonte === "java" || fonte === "go" || fonte === "rust");
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
function ultimoSegmentoSimbolico(caminho: string): string {
|
|
1598
|
-
const partes = caminho.split(".").filter(Boolean);
|
|
1599
|
-
return paraIdentificadorModulo(partes[partes.length - 1] ?? caminho);
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
function pontuarCandidatoDeclarado(candidato: SimboloResolvido, origem: SimboloResolvido["origem"], caminhoDeclarado: string): SimboloCandidatoDrift | undefined {
|
|
1603
|
-
if (candidato.origem !== origem) {
|
|
1604
|
-
return undefined;
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
|
-
const caminhoNormalizado = paraIdentificadorModulo(caminhoDeclarado.replace(/\./g, "_"));
|
|
1608
|
-
const candidatoNormalizado = paraIdentificadorModulo(candidato.caminho.replace(/\./g, "_"));
|
|
1609
|
-
const ultimoDeclarado = ultimoSegmentoSimbolico(caminhoDeclarado);
|
|
1610
|
-
const ultimoCandidato = ultimoSegmentoSimbolico(candidato.caminho);
|
|
1611
|
-
const prefixoDeclarado = caminhoDeclarado.split(".").slice(0, -1).join(".");
|
|
1612
|
-
const prefixoCandidato = candidato.caminho.split(".").slice(0, -1).join(".");
|
|
1613
|
-
|
|
1614
|
-
if (candidato.caminho === caminhoDeclarado) {
|
|
1615
|
-
return {
|
|
1616
|
-
origem: candidato.origem,
|
|
1617
|
-
caminho: candidato.caminho,
|
|
1618
|
-
arquivo: candidato.arquivo,
|
|
1619
|
-
simbolo: candidato.simbolo,
|
|
1620
|
-
confianca: "alta",
|
|
1621
|
-
motivo: "Caminho simbolico bate exatamente com o declarado.",
|
|
1622
|
-
};
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
if (ultimoDeclarado && ultimoDeclarado === ultimoCandidato) {
|
|
1626
|
-
return {
|
|
1627
|
-
origem: candidato.origem,
|
|
1628
|
-
caminho: candidato.caminho,
|
|
1629
|
-
arquivo: candidato.arquivo,
|
|
1630
|
-
simbolo: candidato.simbolo,
|
|
1631
|
-
confianca: "alta",
|
|
1632
|
-
motivo: "Ultimo simbolo bate com a implementacao declarada.",
|
|
1633
|
-
};
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
if (ultimoDeclarado && (candidatoNormalizado.includes(ultimoDeclarado) || caminhoNormalizado.includes(ultimoCandidato))) {
|
|
1637
|
-
return {
|
|
1638
|
-
origem: candidato.origem,
|
|
1639
|
-
caminho: candidato.caminho,
|
|
1640
|
-
arquivo: candidato.arquivo,
|
|
1641
|
-
simbolo: candidato.simbolo,
|
|
1642
|
-
confianca: "media",
|
|
1643
|
-
motivo: "Trecho relevante do caminho simbolico parece compativel com o declarado.",
|
|
1644
|
-
};
|
|
1645
|
-
}
|
|
1646
|
-
|
|
1647
|
-
if (prefixoDeclarado && prefixoDeclarado === prefixoCandidato) {
|
|
1648
|
-
return {
|
|
1649
|
-
origem: candidato.origem,
|
|
1650
|
-
caminho: candidato.caminho,
|
|
1651
|
-
arquivo: candidato.arquivo,
|
|
1652
|
-
simbolo: candidato.simbolo,
|
|
1653
|
-
confianca: "media",
|
|
1654
|
-
motivo: "Prefixo do caminho simbolico bate com a implementacao declarada; o simbolo final pode ter mudado.",
|
|
1655
|
-
};
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
return undefined;
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
function pontuarCandidatoPorTask(candidato: SimboloResolvido, task: string): SimboloCandidatoDrift | undefined {
|
|
1662
|
-
const taskNormalizada = paraIdentificadorModulo(task);
|
|
1663
|
-
const simboloNormalizado = paraIdentificadorModulo(candidato.simbolo.replace(/\./g, "_"));
|
|
1664
|
-
const caminhoNormalizado = paraIdentificadorModulo(candidato.caminho.replace(/\./g, "_"));
|
|
1665
|
-
|
|
1666
|
-
if (!taskNormalizada) {
|
|
1667
|
-
return undefined;
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
|
-
if (simboloNormalizado === taskNormalizada || ultimoSegmentoSimbolico(candidato.caminho) === taskNormalizada) {
|
|
1671
|
-
return {
|
|
1672
|
-
origem: candidato.origem,
|
|
1673
|
-
caminho: candidato.caminho,
|
|
1674
|
-
arquivo: candidato.arquivo,
|
|
1675
|
-
simbolo: candidato.simbolo,
|
|
1676
|
-
confianca: "alta",
|
|
1677
|
-
motivo: "Nome da task bate com o simbolo encontrado no codigo vivo.",
|
|
1678
|
-
};
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
if (simboloNormalizado.includes(taskNormalizada) || taskNormalizada.includes(simboloNormalizado) || caminhoNormalizado.includes(taskNormalizada)) {
|
|
1682
|
-
return {
|
|
1683
|
-
origem: candidato.origem,
|
|
1684
|
-
caminho: candidato.caminho,
|
|
1685
|
-
arquivo: candidato.arquivo,
|
|
1686
|
-
simbolo: candidato.simbolo,
|
|
1687
|
-
confianca: "media",
|
|
1688
|
-
motivo: "Nome da task parece compativel com o simbolo encontrado no codigo vivo.",
|
|
1689
|
-
};
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
return undefined;
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
function deduplicarCandidatos(candidatos: SimboloCandidatoDrift[]): SimboloCandidatoDrift[] {
|
|
1696
|
-
const mapa = new Map<string, SimboloCandidatoDrift>();
|
|
1697
|
-
for (const candidato of candidatos) {
|
|
1698
|
-
const chave = `${candidato.origem}:${candidato.caminho}:${candidato.arquivo}:${candidato.simbolo}`;
|
|
1699
|
-
const anterior = mapa.get(chave);
|
|
1700
|
-
if (!anterior || (anterior.confianca === "media" && candidato.confianca === "alta")) {
|
|
1701
|
-
mapa.set(chave, candidato);
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
return [...mapa.values()];
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
|
-
function ordenarCandidatos(candidatos: SimboloCandidatoDrift[]): SimboloCandidatoDrift[] {
|
|
1708
|
-
return [...candidatos].sort((a, b) => {
|
|
1709
|
-
if (a.confianca !== b.confianca) {
|
|
1710
|
-
return a.confianca === "alta" ? -1 : 1;
|
|
1711
|
-
}
|
|
1712
|
-
return a.caminho.localeCompare(b.caminho, "pt-BR");
|
|
1713
|
-
});
|
|
1714
|
-
}
|
|
1715
|
-
|
|
1716
|
-
function sugerirCandidatosParaImpl(
|
|
1717
|
-
simbolos: SimboloResolvido[],
|
|
1718
|
-
origem: SimboloResolvido["origem"],
|
|
1719
|
-
caminhoDeclarado: string,
|
|
1720
|
-
): SimboloCandidatoDrift[] {
|
|
1721
|
-
return ordenarCandidatos(deduplicarCandidatos(
|
|
1722
|
-
simbolos
|
|
1723
|
-
.map((candidato) => pontuarCandidatoDeclarado(candidato, origem, caminhoDeclarado))
|
|
1724
|
-
.filter((item): item is SimboloCandidatoDrift => Boolean(item)),
|
|
1725
|
-
)).slice(0, 5);
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
function sugerirCandidatosParaTaskSemImpl(simbolos: SimboloResolvido[], nomeTask: string): SimboloCandidatoDrift[] {
|
|
1729
|
-
return ordenarCandidatos(deduplicarCandidatos(
|
|
1730
|
-
simbolos
|
|
1731
|
-
.map((candidato) => pontuarCandidatoPorTask(candidato, nomeTask))
|
|
1732
|
-
.filter((item): item is SimboloCandidatoDrift => Boolean(item)),
|
|
1733
|
-
)).slice(0, 5);
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
function escolherRotasEsperadas(task: IrTask, fontesLegado: FonteLegado[]): Array<"nestjs" | "fastapi" | "flask" | "nextjs" | "firebase" | "dotnet" | "java" | "go" | "rust"> {
|
|
1737
|
-
const fontesTs = extrairFontesHttpTypeScript(fontesLegado);
|
|
1738
|
-
const fontesBackend = extrairFontesHttpBackend(fontesLegado);
|
|
1739
|
-
const implTs = task.implementacoesExternas.find((impl) => impl.origem === "ts");
|
|
1740
|
-
if (implTs) {
|
|
1741
|
-
const esperadas = new Set<"nestjs" | "nextjs" | "firebase">();
|
|
1742
|
-
if (/\.route\.(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)$/i.test(implTs.caminho) || /\.route\./i.test(implTs.caminho)) {
|
|
1743
|
-
esperadas.add("nextjs");
|
|
1744
|
-
}
|
|
1745
|
-
if (/\bcontroller\b/i.test(implTs.caminho) && fontesTs.includes("nestjs")) {
|
|
1746
|
-
esperadas.add("nestjs");
|
|
1747
|
-
}
|
|
1748
|
-
if (fontesTs.includes("firebase") && /(apps\.worker|worker|sema_contract_bridge|health)/i.test(implTs.caminho)) {
|
|
1749
|
-
esperadas.add("firebase");
|
|
1750
|
-
}
|
|
1751
|
-
if (esperadas.size > 0) {
|
|
1752
|
-
return [...esperadas];
|
|
1753
|
-
}
|
|
1754
|
-
if (fontesTs.length > 0) {
|
|
1755
|
-
return fontesTs;
|
|
1756
|
-
}
|
|
1757
|
-
return ["nestjs", "nextjs", "firebase"];
|
|
1758
|
-
}
|
|
1759
|
-
if (task.implementacoesExternas.some((impl) => impl.origem === "py")) {
|
|
1760
|
-
const fontesPython = fontesLegado.filter((fonte): fonte is "fastapi" | "flask" => fonte === "fastapi" || fonte === "flask");
|
|
1761
|
-
if (fontesPython.length > 0) {
|
|
1762
|
-
return fontesPython;
|
|
1763
|
-
}
|
|
1764
|
-
return ["fastapi", "flask"];
|
|
1765
|
-
}
|
|
1766
|
-
const implCs = task.implementacoesExternas.find((impl) => impl.origem === "cs");
|
|
1767
|
-
if (implCs) {
|
|
1768
|
-
return fontesBackend.includes("dotnet") ? ["dotnet"] : ["dotnet"];
|
|
1769
|
-
}
|
|
1770
|
-
const implJava = task.implementacoesExternas.find((impl) => impl.origem === "java");
|
|
1771
|
-
if (implJava) {
|
|
1772
|
-
return fontesBackend.includes("java") ? ["java"] : ["java"];
|
|
1773
|
-
}
|
|
1774
|
-
const implGo = task.implementacoesExternas.find((impl) => impl.origem === "go");
|
|
1775
|
-
if (implGo) {
|
|
1776
|
-
return fontesBackend.includes("go") ? ["go"] : ["go"];
|
|
1777
|
-
}
|
|
1778
|
-
const implRust = task.implementacoesExternas.find((impl) => impl.origem === "rust");
|
|
1779
|
-
if (implRust) {
|
|
1780
|
-
return fontesBackend.includes("rust") ? ["rust"] : ["rust"];
|
|
1781
|
-
}
|
|
1782
|
-
if (fontesTs.length > 0) {
|
|
1783
|
-
return fontesTs;
|
|
1784
|
-
}
|
|
1785
|
-
const fontesPython = fontesLegado.filter((fonte): fonte is "fastapi" | "flask" => fonte === "fastapi" || fonte === "flask");
|
|
1786
|
-
if (fontesPython.length > 0) {
|
|
1787
|
-
return fontesPython;
|
|
1788
|
-
}
|
|
1789
|
-
if (fontesBackend.length > 0) {
|
|
1790
|
-
return fontesBackend;
|
|
1791
|
-
}
|
|
1792
|
-
return [];
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
function taskEhBridgeFirebase(task: IrTask): boolean {
|
|
1796
|
-
return task.implementacoesExternas.some((impl) =>
|
|
1797
|
-
impl.origem === "ts" && /sema_contract_bridge|collections?|apps\.worker/i.test(impl.caminho));
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
|
-
function extrairRecursosEsperados(task: IrTask): Array<{ categoria: "persistencia"; alvo: string }> {
|
|
1801
|
-
if (!taskEhBridgeFirebase(task)) {
|
|
1802
|
-
return [];
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
|
-
return task.efeitosEstruturados
|
|
1806
|
-
.filter((efeito) => efeito.categoria === "persistencia" && Boolean(efeito.alvo))
|
|
1807
|
-
.map((efeito) => ({
|
|
1808
|
-
categoria: "persistencia" as const,
|
|
1809
|
-
alvo: efeito.alvo,
|
|
1810
|
-
}));
|
|
1811
|
-
}
|
|
1812
|
-
|
|
1813
|
-
function coletarVinculosIr(ir: IrModulo): Array<{ donoTipo: RegistroVinculoDrift["donoTipo"]; dono: string; vinculo: IrVinculo }> {
|
|
1814
|
-
return [
|
|
1815
|
-
...ir.vinculos.map((vinculo) => ({ donoTipo: "modulo" as const, dono: ir.nome, vinculo })),
|
|
1816
|
-
...ir.tasks.flatMap((task) => task.vinculos.map((vinculo) => ({ donoTipo: "task" as const, dono: task.nome, vinculo }))),
|
|
1817
|
-
...ir.flows.flatMap((flow: IrFlow) => flow.vinculos.map((vinculo) => ({ donoTipo: "flow" as const, dono: flow.nome, vinculo }))),
|
|
1818
|
-
...ir.routes.flatMap((route: IrRoute) => route.vinculos.map((vinculo) => ({ donoTipo: "route" as const, dono: route.nome, vinculo }))),
|
|
1819
|
-
...ir.superficies.flatMap((superficie: IrSuperficie) => superficie.vinculos.map((vinculo) => ({ donoTipo: "superficie" as const, dono: `${superficie.tipo}:${superficie.nome}`, vinculo }))),
|
|
1820
|
-
];
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
|
-
export async function analisarDriftLegado(contexto: ContextoProjetoCarregado): Promise<ResultadoDrift> {
|
|
1824
|
-
const indexTs = await indexarTypeScript(contexto.diretoriosCodigo);
|
|
1825
|
-
const indexPy = await indexarPython(contexto.diretoriosCodigo);
|
|
1826
|
-
const indexDart = await indexarDart(contexto.diretoriosCodigo);
|
|
1827
|
-
const indexDotnet = await indexarDotnet(contexto.diretoriosCodigo);
|
|
1828
|
-
const indexJava = await indexarJava(contexto.diretoriosCodigo);
|
|
1829
|
-
const indexGo = await indexarGo(contexto.diretoriosCodigo);
|
|
1830
|
-
const indexRust = await indexarRust(contexto.diretoriosCodigo);
|
|
1831
|
-
const indexCpp = await indexarCpp(contexto.diretoriosCodigo);
|
|
1832
|
-
const todosSimbolos = [
|
|
1833
|
-
...indexTs.simbolos,
|
|
1834
|
-
...indexPy.simbolos,
|
|
1835
|
-
...indexDart.simbolos,
|
|
1836
|
-
...indexDotnet.simbolos,
|
|
1837
|
-
...indexJava.simbolos,
|
|
1838
|
-
...indexGo.simbolos,
|
|
1839
|
-
...indexRust.simbolos,
|
|
1840
|
-
...indexCpp,
|
|
1841
|
-
];
|
|
1842
|
-
const mapaImpl = new Map<string, SimboloResolvido>([
|
|
1843
|
-
...indexTs.simbolos.map((item) => [item.caminho, item] as const),
|
|
1844
|
-
...indexPy.simbolos.map((item) => [item.caminho, item] as const),
|
|
1845
|
-
...indexDart.simbolos.map((item) => [item.caminho, item] as const),
|
|
1846
|
-
...indexDotnet.simbolos.map((item) => [item.caminho, item] as const),
|
|
1847
|
-
...indexJava.simbolos.map((item) => [item.caminho, item] as const),
|
|
1848
|
-
...indexGo.simbolos.map((item) => [item.caminho, item] as const),
|
|
1849
|
-
...indexRust.simbolos.map((item) => [item.caminho, item] as const),
|
|
1850
|
-
...indexCpp.map((item) => [item.caminho, item] as const),
|
|
1851
|
-
]);
|
|
1852
|
-
const mapaRecursos = new Map<string, RecursoResolvido>(
|
|
1853
|
-
indexTs.recursos.map((item) => [item.nome, item] as const),
|
|
1854
|
-
);
|
|
1855
|
-
const todasRotasIndexadas = [
|
|
1856
|
-
...indexTs.rotas,
|
|
1857
|
-
...indexPy.rotas,
|
|
1858
|
-
...indexDart.rotas,
|
|
1859
|
-
...indexDotnet.rotas,
|
|
1860
|
-
...indexJava.rotas,
|
|
1861
|
-
...indexGo.rotas,
|
|
1862
|
-
...indexRust.rotas,
|
|
1863
|
-
];
|
|
1864
|
-
const todosArquivosConhecidos = [...new Set([
|
|
1865
|
-
...todosSimbolos.map((item) => item.arquivo),
|
|
1866
|
-
...todasRotasIndexadas.map((item) => item.arquivo),
|
|
1867
|
-
...indexTs.recursos.map((item) => item.arquivo),
|
|
1868
|
-
])].sort((a, b) => a.localeCompare(b, "pt-BR"));
|
|
1869
|
-
|
|
1870
|
-
const implsValidos: RegistroImplDrift[] = [];
|
|
1871
|
-
const implsQuebrados: RegistroImplDrift[] = [];
|
|
1872
|
-
const vinculosValidos: RegistroVinculoDrift[] = [];
|
|
1873
|
-
const vinculosQuebrados: RegistroVinculoDrift[] = [];
|
|
1874
|
-
const rotasDivergentes: RegistroRotaDivergente[] = [];
|
|
1875
|
-
const recursosValidos: RegistroRecursoDrift[] = [];
|
|
1876
|
-
const recursosDivergentes: RegistroRecursoDrift[] = [];
|
|
1877
|
-
const diagnosticos: DiagnosticoDrift[] = [];
|
|
1878
|
-
const tasksResumo: ResultadoDrift["tasks"] = [];
|
|
1879
|
-
const taskPorChave = new Map<string, IrTask>();
|
|
1880
|
-
const guardrailsPorTask = new Map<string, {
|
|
1881
|
-
publica: boolean;
|
|
1882
|
-
sensivel: boolean;
|
|
1883
|
-
auth: boolean;
|
|
1884
|
-
authz: boolean;
|
|
1885
|
-
dados: boolean;
|
|
1886
|
-
audit: boolean;
|
|
1887
|
-
segredos: boolean;
|
|
1888
|
-
forbidden: boolean;
|
|
1889
|
-
dadosSensiveis: boolean;
|
|
1890
|
-
efeitoPrivilegiado: boolean;
|
|
1891
|
-
exigeSegredos: boolean;
|
|
1892
|
-
}>();
|
|
1893
|
-
const resumoVinculosPorTask = new Map<string, { validos: number; quebrados: number; arquivos: Set<string> }>();
|
|
1894
|
-
|
|
1895
|
-
for (const item of contexto.modulosSelecionados) {
|
|
1896
|
-
const ir = item.resultado.ir;
|
|
1897
|
-
if (!ir) {
|
|
1898
|
-
continue;
|
|
1899
|
-
}
|
|
1900
|
-
const superficiesPorChave = new Map<string, IrSuperficie>(
|
|
1901
|
-
ir.superficies.map((superficie) => [`${superficie.tipo}:${superficie.nome}`, superficie]),
|
|
1902
|
-
);
|
|
1903
|
-
for (const task of ir.tasks) {
|
|
1904
|
-
guardrailsPorTask.set(`${ir.nome}:${task.nome}`, {
|
|
1905
|
-
publica: false,
|
|
1906
|
-
sensivel: calcularRiscoOperacional(task) === "alto",
|
|
1907
|
-
auth: task.auth.explicita,
|
|
1908
|
-
authz: task.authz.explicita,
|
|
1909
|
-
dados: task.dados.explicita,
|
|
1910
|
-
audit: task.audit.explicita,
|
|
1911
|
-
segredos: task.segredos.explicita,
|
|
1912
|
-
forbidden: task.forbidden.explicita,
|
|
1913
|
-
dadosSensiveis: Boolean(
|
|
1914
|
-
task.dados.classificacaoPadrao && ["pii", "financeiro", "credencial", "segredo"].includes(task.dados.classificacaoPadrao)
|
|
1915
|
-
|| task.dados.campos.some((campo) => ["pii", "financeiro", "credencial", "segredo"].includes(campo.classificacao))
|
|
1916
|
-
),
|
|
1917
|
-
efeitoPrivilegiado: task.efeitosEstruturados.some((efeito) =>
|
|
1918
|
-
["db.read", "db.write", "queue.publish", "queue.consume", "fs.read", "fs.write", "network.egress", "secret.read", "shell.exec"].includes(efeito.categoria)
|
|
1919
|
-
|| ["alta", "critica"].includes(efeito.criticidade ?? ""),
|
|
1920
|
-
),
|
|
1921
|
-
exigeSegredos: task.efeitosEstruturados.some((efeito) => efeito.categoria === "secret.read")
|
|
1922
|
-
|| Boolean(
|
|
1923
|
-
task.dados.classificacaoPadrao && ["credencial", "segredo"].includes(task.dados.classificacaoPadrao)
|
|
1924
|
-
|| task.dados.campos.some((campo) => ["credencial", "segredo"].includes(campo.classificacao))
|
|
1925
|
-
),
|
|
1926
|
-
});
|
|
1927
|
-
}
|
|
1928
|
-
for (const route of ir.routes) {
|
|
1929
|
-
if (!route.task || route.perfilCompatibilidade !== "publico") {
|
|
1930
|
-
continue;
|
|
1931
|
-
}
|
|
1932
|
-
const guardrails = guardrailsPorTask.get(`${ir.nome}:${route.task}`);
|
|
1933
|
-
if (guardrails) {
|
|
1934
|
-
guardrails.publica = true;
|
|
1935
|
-
guardrails.auth = guardrails.auth || route.auth.explicita;
|
|
1936
|
-
guardrails.authz = guardrails.authz || route.authz.explicita;
|
|
1937
|
-
guardrails.dados = guardrails.dados || route.dados.explicita;
|
|
1938
|
-
guardrails.audit = guardrails.audit || route.audit.explicita;
|
|
1939
|
-
guardrails.segredos = guardrails.segredos || route.segredos.explicita;
|
|
1940
|
-
guardrails.forbidden = guardrails.forbidden || route.forbidden.explicita;
|
|
1941
|
-
guardrails.dadosSensiveis = guardrails.dadosSensiveis || Boolean(
|
|
1942
|
-
route.dados.classificacaoPadrao && ["pii", "financeiro", "credencial", "segredo"].includes(route.dados.classificacaoPadrao)
|
|
1943
|
-
|| route.dados.campos.some((campo) => ["pii", "financeiro", "credencial", "segredo"].includes(campo.classificacao))
|
|
1944
|
-
);
|
|
1945
|
-
guardrails.efeitoPrivilegiado = guardrails.efeitoPrivilegiado || route.efeitosPublicos.some((efeito) =>
|
|
1946
|
-
["db.read", "db.write", "queue.publish", "queue.consume", "fs.read", "fs.write", "network.egress", "secret.read", "shell.exec"].includes(efeito.categoria)
|
|
1947
|
-
|| ["alta", "critica"].includes(efeito.criticidade ?? ""),
|
|
1948
|
-
);
|
|
1949
|
-
guardrails.exigeSegredos = guardrails.exigeSegredos || route.efeitosPublicos.some((efeito) => efeito.categoria === "secret.read")
|
|
1950
|
-
|| Boolean(
|
|
1951
|
-
route.dados.classificacaoPadrao && ["credencial", "segredo"].includes(route.dados.classificacaoPadrao)
|
|
1952
|
-
|| route.dados.campos.some((campo) => ["credencial", "segredo"].includes(campo.classificacao))
|
|
1953
|
-
);
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
for (const superficie of ir.superficies) {
|
|
1957
|
-
if (!superficie.task || superficie.perfilCompatibilidade !== "publico") {
|
|
1958
|
-
continue;
|
|
1959
|
-
}
|
|
1960
|
-
const guardrails = guardrailsPorTask.get(`${ir.nome}:${superficie.task}`);
|
|
1961
|
-
if (guardrails) {
|
|
1962
|
-
guardrails.publica = true;
|
|
1963
|
-
guardrails.auth = guardrails.auth || superficie.auth.explicita;
|
|
1964
|
-
guardrails.authz = guardrails.authz || superficie.authz.explicita;
|
|
1965
|
-
guardrails.dados = guardrails.dados || superficie.dados.explicita;
|
|
1966
|
-
guardrails.audit = guardrails.audit || superficie.audit.explicita;
|
|
1967
|
-
guardrails.segredos = guardrails.segredos || superficie.segredos.explicita;
|
|
1968
|
-
guardrails.forbidden = guardrails.forbidden || superficie.forbidden.explicita;
|
|
1969
|
-
guardrails.dadosSensiveis = guardrails.dadosSensiveis || Boolean(
|
|
1970
|
-
superficie.dados.classificacaoPadrao && ["pii", "financeiro", "credencial", "segredo"].includes(superficie.dados.classificacaoPadrao)
|
|
1971
|
-
|| superficie.dados.campos.some((campo) => ["pii", "financeiro", "credencial", "segredo"].includes(campo.classificacao))
|
|
1972
|
-
);
|
|
1973
|
-
guardrails.efeitoPrivilegiado = guardrails.efeitoPrivilegiado || superficie.effects.some((efeito) =>
|
|
1974
|
-
["db.read", "db.write", "queue.publish", "queue.consume", "fs.read", "fs.write", "network.egress", "secret.read", "shell.exec"].includes(efeito.categoria)
|
|
1975
|
-
|| ["alta", "critica"].includes(efeito.criticidade ?? ""),
|
|
1976
|
-
);
|
|
1977
|
-
guardrails.exigeSegredos = guardrails.exigeSegredos || superficie.effects.some((efeito) => efeito.categoria === "secret.read")
|
|
1978
|
-
|| Boolean(
|
|
1979
|
-
superficie.dados.classificacaoPadrao && ["credencial", "segredo"].includes(superficie.dados.classificacaoPadrao)
|
|
1980
|
-
|| superficie.dados.campos.some((campo) => ["credencial", "segredo"].includes(campo.classificacao))
|
|
1981
|
-
);
|
|
1982
|
-
}
|
|
1983
|
-
}
|
|
1984
|
-
|
|
1985
|
-
for (const task of ir.tasks) {
|
|
1986
|
-
taskPorChave.set(`${ir.nome}:${task.nome}`, task);
|
|
1987
|
-
let validos = 0;
|
|
1988
|
-
let quebrados = 0;
|
|
1989
|
-
const arquivosReferenciados = new Set<string>();
|
|
1990
|
-
const simbolosReferenciados = new Set<string>();
|
|
1991
|
-
const candidatosTask = new Map<string, SimboloCandidatoDrift>();
|
|
1992
|
-
|
|
1993
|
-
if (task.implementacoesExternas.length === 0) {
|
|
1994
|
-
for (const candidato of sugerirCandidatosParaTaskSemImpl(todosSimbolos, task.nome)) {
|
|
1995
|
-
candidatosTask.set(`${candidato.origem}:${candidato.caminho}:${candidato.arquivo}:${candidato.simbolo}`, candidato);
|
|
1996
|
-
}
|
|
1997
|
-
diagnosticos.push({
|
|
1998
|
-
tipo: "task_sem_impl",
|
|
1999
|
-
modulo: ir.nome,
|
|
2000
|
-
task: task.nome,
|
|
2001
|
-
mensagem: `Task "${task.nome}" ainda nao foi ligada a nenhuma implementacao externa.`,
|
|
2002
|
-
});
|
|
2003
|
-
}
|
|
2004
|
-
|
|
2005
|
-
for (const impl of task.implementacoesExternas) {
|
|
2006
|
-
const resolvido = mapaImpl.get(impl.caminho);
|
|
2007
|
-
const registro: RegistroImplDrift = {
|
|
2008
|
-
modulo: ir.nome,
|
|
2009
|
-
task: task.nome,
|
|
2010
|
-
origem: impl.origem,
|
|
2011
|
-
caminho: impl.caminho,
|
|
2012
|
-
arquivo: resolvido?.arquivo,
|
|
2013
|
-
simbolo: resolvido?.simbolo,
|
|
2014
|
-
caminhoResolvido: resolvido?.caminho,
|
|
2015
|
-
status: resolvido ? "resolvido" : "quebrado",
|
|
2016
|
-
};
|
|
2017
|
-
|
|
2018
|
-
if (resolvido) {
|
|
2019
|
-
arquivosReferenciados.add(resolvido.arquivo);
|
|
2020
|
-
simbolosReferenciados.add(resolvido.simbolo);
|
|
2021
|
-
implsValidos.push(registro);
|
|
2022
|
-
validos += 1;
|
|
2023
|
-
} else {
|
|
2024
|
-
registro.candidatos = sugerirCandidatosParaImpl(todosSimbolos, impl.origem, impl.caminho);
|
|
2025
|
-
for (const candidato of registro.candidatos) {
|
|
2026
|
-
candidatosTask.set(`${candidato.origem}:${candidato.caminho}:${candidato.arquivo}:${candidato.simbolo}`, candidato);
|
|
2027
|
-
}
|
|
2028
|
-
implsQuebrados.push(registro);
|
|
2029
|
-
quebrados += 1;
|
|
2030
|
-
diagnosticos.push({
|
|
2031
|
-
tipo: "impl_quebrado",
|
|
2032
|
-
modulo: ir.nome,
|
|
2033
|
-
task: task.nome,
|
|
2034
|
-
mensagem: `Implementacao externa "${impl.origem}:${impl.caminho}" nao foi encontrada nos diretorios de codigo vivos.`,
|
|
2035
|
-
});
|
|
2036
|
-
}
|
|
2037
|
-
}
|
|
2038
|
-
|
|
2039
|
-
tasksResumo.push({
|
|
2040
|
-
modulo: ir.nome,
|
|
2041
|
-
task: task.nome,
|
|
2042
|
-
impls: task.implementacoesExternas.length,
|
|
2043
|
-
implsValidos: validos,
|
|
2044
|
-
implsQuebrados: quebrados,
|
|
2045
|
-
semImplementacao: task.implementacoesExternas.length === 0,
|
|
2046
|
-
scoreSemantico: 0,
|
|
2047
|
-
confiancaVinculo: "baixa",
|
|
2048
|
-
riscoOperacional: "baixo",
|
|
2049
|
-
lacunas: [],
|
|
2050
|
-
arquivosReferenciados: [...arquivosReferenciados].sort((a, b) => a.localeCompare(b, "pt-BR")),
|
|
2051
|
-
arquivosProvaveisEditar: [],
|
|
2052
|
-
simbolosReferenciados: [...simbolosReferenciados].sort((a, b) => a.localeCompare(b, "pt-BR")),
|
|
2053
|
-
candidatosImpl: ordenarCandidatos([...candidatosTask.values()]).slice(0, 5),
|
|
2054
|
-
checksSugeridos: [],
|
|
2055
|
-
});
|
|
2056
|
-
|
|
2057
|
-
for (const recursoEsperado of extrairRecursosEsperados(task)) {
|
|
2058
|
-
const resolvido = mapaRecursos.get(recursoEsperado.alvo);
|
|
2059
|
-
const registro: RegistroRecursoDrift = {
|
|
2060
|
-
modulo: ir.nome,
|
|
2061
|
-
task: task.nome,
|
|
2062
|
-
categoria: recursoEsperado.categoria,
|
|
2063
|
-
alvo: recursoEsperado.alvo,
|
|
2064
|
-
arquivo: resolvido?.arquivo ?? "",
|
|
2065
|
-
origem: "firebase",
|
|
2066
|
-
tipo: "colecao",
|
|
2067
|
-
status: resolvido ? "resolvido" : "divergente",
|
|
2068
|
-
};
|
|
2069
|
-
|
|
2070
|
-
if (resolvido) {
|
|
2071
|
-
registro.arquivo = resolvido.arquivo;
|
|
2072
|
-
recursosValidos.push(registro);
|
|
2073
|
-
} else {
|
|
2074
|
-
recursosDivergentes.push(registro);
|
|
2075
|
-
diagnosticos.push({
|
|
2076
|
-
tipo: "recurso_divergente",
|
|
2077
|
-
modulo: ir.nome,
|
|
2078
|
-
task: task.nome,
|
|
2079
|
-
mensagem: `Recurso vivo "${recursoEsperado.alvo}" nao foi encontrado nos bridges/configuracoes Firebase do codigo legado.`,
|
|
2080
|
-
});
|
|
2081
|
-
}
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
|
|
2085
|
-
for (const route of ir.routes) {
|
|
2086
|
-
const taskAssociada = ir.tasks.find((task) => task.nome === route.task);
|
|
2087
|
-
const esperadas = escolherRotasEsperadas(taskAssociada ?? {
|
|
2088
|
-
nome: "",
|
|
2089
|
-
input: [],
|
|
2090
|
-
output: [],
|
|
2091
|
-
rules: [],
|
|
2092
|
-
regrasEstruturadas: [],
|
|
2093
|
-
effects: [],
|
|
2094
|
-
efeitosEstruturados: [],
|
|
2095
|
-
implementacoesExternas: [],
|
|
2096
|
-
vinculos: [],
|
|
2097
|
-
execucao: {
|
|
2098
|
-
idempotencia: false,
|
|
2099
|
-
timeout: "padrao",
|
|
2100
|
-
retry: "nenhum",
|
|
2101
|
-
compensacao: "nenhuma",
|
|
2102
|
-
criticidadeOperacional: "media",
|
|
2103
|
-
explicita: false,
|
|
2104
|
-
},
|
|
2105
|
-
auth: {
|
|
2106
|
-
explicita: false,
|
|
2107
|
-
},
|
|
2108
|
-
authz: {
|
|
2109
|
-
explicita: false,
|
|
2110
|
-
papeis: [],
|
|
2111
|
-
escopos: [],
|
|
2112
|
-
},
|
|
2113
|
-
dados: {
|
|
2114
|
-
explicita: false,
|
|
2115
|
-
campos: [],
|
|
2116
|
-
},
|
|
2117
|
-
audit: {
|
|
2118
|
-
explicita: false,
|
|
2119
|
-
},
|
|
2120
|
-
segredos: {
|
|
2121
|
-
explicita: false,
|
|
2122
|
-
itens: [],
|
|
2123
|
-
},
|
|
2124
|
-
forbidden: {
|
|
2125
|
-
explicita: false,
|
|
2126
|
-
regras: [],
|
|
2127
|
-
},
|
|
2128
|
-
guarantees: [],
|
|
2129
|
-
garantiasEstruturadas: [],
|
|
2130
|
-
errors: {},
|
|
2131
|
-
errosDetalhados: [],
|
|
2132
|
-
perfilCompatibilidade: "interno",
|
|
2133
|
-
resumoAgente: {
|
|
2134
|
-
riscos: [],
|
|
2135
|
-
checks: [],
|
|
2136
|
-
entidadesAfetadas: [],
|
|
2137
|
-
superficiesPublicas: [],
|
|
2138
|
-
mutacoesPrevistas: [],
|
|
2139
|
-
},
|
|
2140
|
-
tests: [],
|
|
2141
|
-
}, contexto.fontesLegado);
|
|
2142
|
-
|
|
2143
|
-
if (!esperadas.length || !route.metodo || !route.caminho) {
|
|
2144
|
-
continue;
|
|
2145
|
-
}
|
|
2146
|
-
|
|
2147
|
-
const encontradas = todasRotasIndexadas.filter((rotaResolvida) =>
|
|
2148
|
-
rotaResolvida.origem !== "nextjs-consumer"
|
|
2149
|
-
&& rotaResolvida.origem !== "react-vite-consumer"
|
|
2150
|
-
&& rotaResolvida.origem !== "angular-consumer"
|
|
2151
|
-
&& rotaResolvida.origem !== "flutter-consumer"
|
|
2152
|
-
&& esperadas.includes(rotaResolvida.origem));
|
|
2153
|
-
const combina = encontradas.some((rotaResolvida) =>
|
|
2154
|
-
rotaResolvida.metodo === route.metodo
|
|
2155
|
-
&& normalizarCaminhoRota(rotaResolvida.caminho) === normalizarCaminhoRota(route.caminho));
|
|
2156
|
-
|
|
2157
|
-
if (!combina) {
|
|
2158
|
-
const registro = {
|
|
2159
|
-
modulo: ir.nome,
|
|
2160
|
-
route: route.nome,
|
|
2161
|
-
metodo: route.metodo,
|
|
2162
|
-
caminho: route.caminho,
|
|
2163
|
-
motivo: `Nenhuma rota publica ${route.metodo} ${route.caminho} foi encontrada no codigo legado para o framework esperado.`,
|
|
2164
|
-
};
|
|
2165
|
-
rotasDivergentes.push(registro);
|
|
2166
|
-
diagnosticos.push({
|
|
2167
|
-
tipo: "rota_divergente",
|
|
2168
|
-
modulo: ir.nome,
|
|
2169
|
-
route: route.nome,
|
|
2170
|
-
mensagem: registro.motivo,
|
|
2171
|
-
});
|
|
2172
|
-
}
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
|
-
for (const itemVinculo of coletarVinculosIr(ir)) {
|
|
2176
|
-
const registro: RegistroVinculoDrift = {
|
|
2177
|
-
modulo: ir.nome,
|
|
2178
|
-
donoTipo: itemVinculo.donoTipo,
|
|
2179
|
-
dono: itemVinculo.dono,
|
|
2180
|
-
tipo: itemVinculo.vinculo.tipo,
|
|
2181
|
-
valor: itemVinculo.vinculo.valor,
|
|
2182
|
-
status: "nao_encontrado",
|
|
2183
|
-
confianca: "baixa",
|
|
2184
|
-
};
|
|
2185
|
-
|
|
2186
|
-
const arquivoDeclarado = itemVinculo.vinculo.arquivo ?? (itemVinculo.vinculo.tipo === "arquivo" ? itemVinculo.vinculo.valor : undefined);
|
|
2187
|
-
const simboloDeclarado = itemVinculo.vinculo.simbolo ?? (itemVinculo.vinculo.tipo === "simbolo" ? itemVinculo.vinculo.valor : undefined);
|
|
2188
|
-
const recursoDeclarado = itemVinculo.vinculo.recurso ?? (["recurso", "tabela", "fila", "cache", "storage"].includes(itemVinculo.vinculo.tipo) ? itemVinculo.vinculo.valor : undefined);
|
|
2189
|
-
const superficieDeclarada = itemVinculo.vinculo.superficie ?? (["superficie", "rota", "worker", "cron", "webhook", "evento", "policy", "fila", "cache", "storage"].includes(itemVinculo.vinculo.tipo) ? itemVinculo.vinculo.valor : undefined);
|
|
2190
|
-
|
|
2191
|
-
if (simboloDeclarado) {
|
|
2192
|
-
const resolucaoSimbolo = escolherSimboloPorVinculo(todosSimbolos, mapaImpl, simboloDeclarado);
|
|
2193
|
-
registro.status = resolucaoSimbolo.status;
|
|
2194
|
-
registro.confianca = resolucaoSimbolo.confianca;
|
|
2195
|
-
registro.arquivo = resolucaoSimbolo.simbolo?.arquivo;
|
|
2196
|
-
registro.simbolo = resolucaoSimbolo.simbolo?.simbolo;
|
|
2197
|
-
} else if (arquivoDeclarado) {
|
|
2198
|
-
const resolucaoArquivo = escolherArquivoPorVinculo(todosArquivosConhecidos, arquivoDeclarado);
|
|
2199
|
-
registro.status = resolucaoArquivo.status;
|
|
2200
|
-
registro.confianca = resolucaoArquivo.confianca;
|
|
2201
|
-
registro.arquivo = resolucaoArquivo.arquivo;
|
|
2202
|
-
} else if (recursoDeclarado) {
|
|
2203
|
-
const recurso = mapaRecursos.get(recursoDeclarado);
|
|
2204
|
-
if (recurso) {
|
|
2205
|
-
registro.status = "resolvido";
|
|
2206
|
-
registro.confianca = "alta";
|
|
2207
|
-
registro.arquivo = recurso.arquivo;
|
|
2208
|
-
}
|
|
2209
|
-
} else if (superficieDeclarada) {
|
|
2210
|
-
const rota = todasRotasIndexadas.find((rotaResolvida) =>
|
|
2211
|
-
normalizarCaminhoRota(rotaResolvida.caminho) === normalizarCaminhoRota(superficieDeclarada));
|
|
2212
|
-
if (rota) {
|
|
2213
|
-
registro.status = "resolvido";
|
|
2214
|
-
registro.confianca = "alta";
|
|
2215
|
-
registro.arquivo = rota.arquivo;
|
|
2216
|
-
registro.simbolo = rota.simbolo;
|
|
2217
|
-
} else {
|
|
2218
|
-
const resolucaoArquivo = escolherArquivoPorVinculo(todosArquivosConhecidos, superficieDeclarada);
|
|
2219
|
-
registro.status = resolucaoArquivo.status;
|
|
2220
|
-
registro.confianca = resolucaoArquivo.confianca;
|
|
2221
|
-
registro.arquivo = resolucaoArquivo.arquivo;
|
|
2222
|
-
}
|
|
2223
|
-
} else {
|
|
2224
|
-
const resolucaoArquivo = escolherArquivoPorVinculo(todosArquivosConhecidos, itemVinculo.vinculo.valor);
|
|
2225
|
-
registro.status = resolucaoArquivo.status;
|
|
2226
|
-
registro.confianca = resolucaoArquivo.confianca;
|
|
2227
|
-
registro.arquivo = resolucaoArquivo.arquivo;
|
|
2228
|
-
}
|
|
2229
|
-
|
|
2230
|
-
if (registro.status === "nao_encontrado" && itemVinculo.donoTipo === "superficie") {
|
|
2231
|
-
const superficie = superficiesPorChave.get(itemVinculo.dono);
|
|
2232
|
-
const ancora = superficie
|
|
2233
|
-
? encontrarAncoraSuperficie(ir, superficie, todosSimbolos, mapaImpl, todosArquivosConhecidos)
|
|
2234
|
-
: undefined;
|
|
2235
|
-
if (ancora) {
|
|
2236
|
-
registro.status = "parcial";
|
|
2237
|
-
registro.confianca = ancora.confianca === "alta" ? "media" : ancora.confianca;
|
|
2238
|
-
registro.arquivo = registro.arquivo ?? ancora.arquivo;
|
|
2239
|
-
registro.simbolo = registro.simbolo ?? ancora.simbolo;
|
|
2240
|
-
}
|
|
2241
|
-
}
|
|
2242
|
-
|
|
2243
|
-
if (registro.status === "nao_encontrado") {
|
|
2244
|
-
vinculosQuebrados.push(registro);
|
|
2245
|
-
diagnosticos.push({
|
|
2246
|
-
tipo: "vinculo_quebrado",
|
|
2247
|
-
modulo: ir.nome,
|
|
2248
|
-
mensagem: `Vinculo ${registro.tipo}="${registro.valor}" de ${registro.donoTipo} "${registro.dono}" nao foi resolvido no codigo vivo.`,
|
|
2249
|
-
...(itemVinculo.donoTipo === "task" ? { task: itemVinculo.dono } : itemVinculo.donoTipo === "route" ? { route: itemVinculo.dono } : {}),
|
|
2250
|
-
});
|
|
2251
|
-
} else {
|
|
2252
|
-
vinculosValidos.push(registro);
|
|
2253
|
-
}
|
|
2254
|
-
|
|
2255
|
-
if (itemVinculo.donoTipo === "task") {
|
|
2256
|
-
const chaveTask = `${ir.nome}:${itemVinculo.dono}`;
|
|
2257
|
-
const resumo = resumoVinculosPorTask.get(chaveTask) ?? {
|
|
2258
|
-
validos: 0,
|
|
2259
|
-
quebrados: 0,
|
|
2260
|
-
arquivos: new Set<string>(),
|
|
2261
|
-
};
|
|
2262
|
-
if (registro.status === "nao_encontrado") {
|
|
2263
|
-
resumo.quebrados += 1;
|
|
2264
|
-
} else {
|
|
2265
|
-
resumo.validos += 1;
|
|
2266
|
-
}
|
|
2267
|
-
if (registro.arquivo) {
|
|
2268
|
-
resumo.arquivos.add(registro.arquivo);
|
|
2269
|
-
}
|
|
2270
|
-
resumoVinculosPorTask.set(chaveTask, resumo);
|
|
2271
|
-
}
|
|
2272
|
-
}
|
|
2273
|
-
}
|
|
2274
|
-
|
|
2275
|
-
for (const resumo of tasksResumo) {
|
|
2276
|
-
const chaveTask = `${resumo.modulo}:${resumo.task}`;
|
|
2277
|
-
const task = taskPorChave.get(chaveTask);
|
|
2278
|
-
const guardrails = guardrailsPorTask.get(chaveTask) ?? {
|
|
2279
|
-
publica: false,
|
|
2280
|
-
sensivel: false,
|
|
2281
|
-
auth: false,
|
|
2282
|
-
authz: false,
|
|
2283
|
-
dados: false,
|
|
2284
|
-
audit: false,
|
|
2285
|
-
segredos: false,
|
|
2286
|
-
forbidden: false,
|
|
2287
|
-
dadosSensiveis: false,
|
|
2288
|
-
efeitoPrivilegiado: false,
|
|
2289
|
-
exigeSegredos: false,
|
|
2290
|
-
};
|
|
2291
|
-
const resumoVinculos = resumoVinculosPorTask.get(chaveTask) ?? {
|
|
2292
|
-
validos: 0,
|
|
2293
|
-
quebrados: 0,
|
|
2294
|
-
arquivos: new Set<string>(),
|
|
2295
|
-
};
|
|
2296
|
-
if (!task) {
|
|
2297
|
-
continue;
|
|
2298
|
-
}
|
|
2299
|
-
|
|
2300
|
-
resumo.confiancaVinculo = calcularConfiancaTask(task, resumo.implsValidos, resumo.implsQuebrados, resumoVinculos.validos, resumoVinculos.quebrados);
|
|
2301
|
-
resumo.riscoOperacional = calcularRiscoOperacional(task);
|
|
2302
|
-
resumo.lacunas = resumirLacunasTask(task, resumo.semImplementacao, resumo.implsQuebrados, resumoVinculos.quebrados, guardrails);
|
|
2303
|
-
resumo.scoreSemantico = calcularScoreTask(task, resumo.implsValidos, resumo.implsQuebrados, resumoVinculos.validos, resumoVinculos.quebrados, resumo.semImplementacao);
|
|
2304
|
-
resumo.arquivosProvaveisEditar = [...new Set([
|
|
2305
|
-
...resumo.arquivosReferenciados,
|
|
2306
|
-
...resumo.candidatosImpl.map((candidato) => candidato.arquivo),
|
|
2307
|
-
...resumoVinculos.arquivos,
|
|
2308
|
-
])].sort((a, b) => a.localeCompare(b, "pt-BR"));
|
|
2309
|
-
resumo.checksSugeridos = [...new Set([
|
|
2310
|
-
...task.resumoAgente.checks,
|
|
2311
|
-
resumo.riscoOperacional !== "baixo" ? "revisar efeitos operacionais" : "",
|
|
2312
|
-
resumo.lacunas.includes("vinculo_quebrado") ? "corrigir vinculos rastreaveis" : "",
|
|
2313
|
-
resumo.lacunas.some((lacuna) => ["superficie_publica_sem_execucao", "execucao_critica_sem_bloco", "rastreabilidade_fraca"].includes(lacuna))
|
|
2314
|
-
? "endurecer execucao e rastreabilidade para producao"
|
|
2315
|
-
: "",
|
|
2316
|
-
resumo.lacunas.some((lacuna) => ["auth_ausente", "authz_frouxa", "dados_nao_classificados", "audit_ausente", "segredo_sem_governanca", "proibicoes_ausentes"].includes(lacuna))
|
|
2317
|
-
? "explicitar contratos de seguranca semantica"
|
|
2318
|
-
: "",
|
|
2319
|
-
].filter(Boolean))];
|
|
2320
|
-
|
|
2321
|
-
if (resumo.lacunas.includes("superficie_publica_sem_execucao")) {
|
|
2322
|
-
diagnosticos.push({
|
|
2323
|
-
tipo: "seguranca_frouxa",
|
|
2324
|
-
modulo: resumo.modulo,
|
|
2325
|
-
task: resumo.task,
|
|
2326
|
-
mensagem: `Task "${resumo.task}" alimenta superficie publica, mas ainda depende de execucao implicita.`,
|
|
2327
|
-
});
|
|
2328
|
-
}
|
|
2329
|
-
if (resumo.lacunas.includes("execucao_critica_sem_bloco")) {
|
|
2330
|
-
diagnosticos.push({
|
|
2331
|
-
tipo: "seguranca_frouxa",
|
|
2332
|
-
modulo: resumo.modulo,
|
|
2333
|
-
task: resumo.task,
|
|
2334
|
-
mensagem: `Task "${resumo.task}" opera com risco alto, mas ainda nao declarou execucao explicita.`,
|
|
2335
|
-
});
|
|
2336
|
-
}
|
|
2337
|
-
if (resumo.lacunas.includes("rastreabilidade_fraca")) {
|
|
2338
|
-
diagnosticos.push({
|
|
2339
|
-
tipo: "seguranca_frouxa",
|
|
2340
|
-
modulo: resumo.modulo,
|
|
2341
|
-
task: resumo.task,
|
|
2342
|
-
mensagem: `Task "${resumo.task}" exige producao mais rastreavel, mas ainda nao declara impl nem vinculos.`,
|
|
2343
|
-
});
|
|
2344
|
-
}
|
|
2345
|
-
if (resumo.lacunas.includes("auth_ausente")) {
|
|
2346
|
-
diagnosticos.push({
|
|
2347
|
-
tipo: "seguranca_frouxa",
|
|
2348
|
-
modulo: resumo.modulo,
|
|
2349
|
-
task: resumo.task,
|
|
2350
|
-
mensagem: `Task "${resumo.task}" chega em superficie publica sem auth explicita em task, route ou superficie associada.`,
|
|
2351
|
-
});
|
|
2352
|
-
}
|
|
2353
|
-
if (resumo.lacunas.includes("authz_frouxa")) {
|
|
2354
|
-
diagnosticos.push({
|
|
2355
|
-
tipo: "seguranca_frouxa",
|
|
2356
|
-
modulo: resumo.modulo,
|
|
2357
|
-
task: resumo.task,
|
|
2358
|
-
mensagem: `Task "${resumo.task}" opera com risco ou exposicao, mas ainda nao explicita authz suficiente.`,
|
|
2359
|
-
});
|
|
2360
|
-
}
|
|
2361
|
-
if (resumo.lacunas.includes("dados_nao_classificados")) {
|
|
2362
|
-
diagnosticos.push({
|
|
2363
|
-
tipo: "seguranca_frouxa",
|
|
2364
|
-
modulo: resumo.modulo,
|
|
2365
|
-
task: resumo.task,
|
|
2366
|
-
mensagem: `Task "${resumo.task}" ainda nao classifica dados de entrada/saida de forma semantica.`,
|
|
2367
|
-
});
|
|
2368
|
-
}
|
|
2369
|
-
if (resumo.lacunas.includes("audit_ausente")) {
|
|
2370
|
-
diagnosticos.push({
|
|
2371
|
-
tipo: "seguranca_frouxa",
|
|
2372
|
-
modulo: resumo.modulo,
|
|
2373
|
-
task: resumo.task,
|
|
2374
|
-
mensagem: `Task "${resumo.task}" ainda nao declara audit explicita para operacao sensivel ou publica.`,
|
|
2375
|
-
});
|
|
2376
|
-
}
|
|
2377
|
-
if (resumo.lacunas.includes("segredo_sem_governanca")) {
|
|
2378
|
-
diagnosticos.push({
|
|
2379
|
-
tipo: "seguranca_frouxa",
|
|
2380
|
-
modulo: resumo.modulo,
|
|
2381
|
-
task: resumo.task,
|
|
2382
|
-
mensagem: `Task "${resumo.task}" toca segredo ou credencial sem bloco segredos governando origem, escopo e rotacao.`,
|
|
2383
|
-
});
|
|
2384
|
-
}
|
|
2385
|
-
if (resumo.lacunas.includes("proibicoes_ausentes")) {
|
|
2386
|
-
diagnosticos.push({
|
|
2387
|
-
tipo: "seguranca_frouxa",
|
|
2388
|
-
modulo: resumo.modulo,
|
|
2389
|
-
task: resumo.task,
|
|
2390
|
-
mensagem: `Task "${resumo.task}" opera com efeito privilegiado ou dado sensivel sem forbidden explicito para conter abuso e vazamento.`,
|
|
2391
|
-
});
|
|
2392
|
-
}
|
|
2393
|
-
}
|
|
2394
|
-
|
|
2395
|
-
const consumerSurfaces = [...indexTs.consumerSurfaces, ...indexDart.consumerSurfaces].sort((a, b) =>
|
|
2396
|
-
a.rota.localeCompare(b.rota, "pt-BR")
|
|
2397
|
-
|| a.tipoArquivo.localeCompare(b.tipoArquivo, "pt-BR")
|
|
2398
|
-
|| a.arquivo.localeCompare(b.arquivo, "pt-BR"));
|
|
2399
|
-
const consumerBridges = [...new Map(
|
|
2400
|
-
[...indexTs.simbolos, ...indexDart.simbolos]
|
|
2401
|
-
.filter((simbolo) => simboloEhBridgeConsumer(simbolo.caminho, simbolo.arquivo))
|
|
2402
|
-
.map((simbolo) => [
|
|
2403
|
-
`${simbolo.caminho}:${simbolo.arquivo}:${simbolo.simbolo}`,
|
|
2404
|
-
{
|
|
2405
|
-
caminho: simbolo.caminho,
|
|
2406
|
-
arquivo: simbolo.arquivo,
|
|
2407
|
-
simbolo: simbolo.simbolo,
|
|
2408
|
-
},
|
|
2409
|
-
] as const),
|
|
2410
|
-
).values()].sort((a, b) =>
|
|
2411
|
-
a.caminho.localeCompare(b.caminho, "pt-BR")
|
|
2412
|
-
|| a.arquivo.localeCompare(b.arquivo, "pt-BR"));
|
|
2413
|
-
const appRoutes = [...new Set(consumerSurfaces.map((surface) => surface.rota))]
|
|
2414
|
-
.sort((a, b) => a.localeCompare(b, "pt-BR"));
|
|
2415
|
-
const consumerFramework = inferirConsumerFrameworkPrincipal(contexto.fontesLegado, consumerSurfaces, consumerBridges);
|
|
2416
|
-
|
|
2417
|
-
const payloadBase: ResultadoDrift = {
|
|
2418
|
-
comando: "drift",
|
|
2419
|
-
sucesso: implsQuebrados.length === 0 && rotasDivergentes.length === 0 && recursosDivergentes.length === 0 && vinculosQuebrados.length === 0,
|
|
2420
|
-
consumerFramework,
|
|
2421
|
-
appRoutes,
|
|
2422
|
-
consumerSurfaces,
|
|
2423
|
-
consumerBridges,
|
|
2424
|
-
modulos: contexto.modulosSelecionados.map((item) => ({
|
|
2425
|
-
caminho: item.caminho,
|
|
2426
|
-
modulo: item.resultado.ir?.nome ?? item.resultado.modulo?.nome ?? null,
|
|
2427
|
-
tasks: item.resultado.ir?.tasks.length ?? 0,
|
|
2428
|
-
routes: item.resultado.ir?.routes.length ?? 0,
|
|
2429
|
-
})),
|
|
2430
|
-
tasks: tasksResumo,
|
|
2431
|
-
impls_validos: implsValidos,
|
|
2432
|
-
impls_quebrados: implsQuebrados,
|
|
2433
|
-
vinculos_validos: vinculosValidos,
|
|
2434
|
-
vinculos_quebrados: vinculosQuebrados,
|
|
2435
|
-
rotas_divergentes: rotasDivergentes,
|
|
2436
|
-
recursos_validos: recursosValidos,
|
|
2437
|
-
recursos_divergentes: recursosDivergentes,
|
|
2438
|
-
diagnosticos,
|
|
2439
|
-
resumo_operacional: {
|
|
2440
|
-
scoreMedio: 0,
|
|
2441
|
-
confiancaGeral: "baixa" as const,
|
|
2442
|
-
riscosPrincipais: [],
|
|
2443
|
-
oQueTocar: [],
|
|
2444
|
-
oQueValidar: [],
|
|
2445
|
-
oQueEstaFrouxo: [],
|
|
2446
|
-
oQueFoiInferido: [],
|
|
2447
|
-
},
|
|
2448
|
-
};
|
|
2449
|
-
const resumoOperacional = resumirOperacional(payloadBase);
|
|
2450
|
-
|
|
2451
|
-
return {
|
|
2452
|
-
...payloadBase,
|
|
2453
|
-
resumo_operacional: resumoOperacional,
|
|
2454
|
-
};
|
|
2455
|
-
}
|