@semacode/cli 1.3.7 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +61 -41
  2. package/dist/drift.d.ts +5 -3
  3. package/dist/drift.js +532 -27
  4. package/dist/drift.js.map +1 -1
  5. package/dist/importador.d.ts +2 -0
  6. package/dist/importador.js +166 -7
  7. package/dist/importador.js.map +1 -1
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/projeto.d.ts +6 -1
  11. package/dist/projeto.js.map +1 -1
  12. package/docs/cli.md +101 -0
  13. package/docs/instalacao-e-primeiro-uso.md +108 -196
  14. package/docs/integracao-com-ia.md +43 -200
  15. package/docs/persistencia-vendor-first.md +145 -0
  16. package/docs/sintaxe.md +67 -251
  17. package/exemplos/persistencia_vendor_first.sema +86 -0
  18. package/node_modules/@sema/gerador-css/package.json +1 -1
  19. package/node_modules/@sema/gerador-dart/package.json +1 -1
  20. package/node_modules/@sema/gerador-html/package.json +1 -1
  21. package/node_modules/@sema/gerador-javascript/package.json +1 -1
  22. package/node_modules/@sema/gerador-lua/package.json +1 -1
  23. package/node_modules/@sema/gerador-python/package.json +1 -1
  24. package/node_modules/@sema/gerador-typescript/package.json +1 -1
  25. package/node_modules/@sema/nucleo/dist/ast/tipos.d.ts +2 -1
  26. package/node_modules/@sema/nucleo/dist/formatador/index.js +32 -17
  27. package/node_modules/@sema/nucleo/dist/formatador/index.js.map +1 -1
  28. package/node_modules/@sema/nucleo/dist/index.d.ts +1 -0
  29. package/node_modules/@sema/nucleo/dist/index.js +1 -0
  30. package/node_modules/@sema/nucleo/dist/index.js.map +1 -1
  31. package/node_modules/@sema/nucleo/dist/ir/conversor.js +94 -0
  32. package/node_modules/@sema/nucleo/dist/ir/conversor.js.map +1 -1
  33. package/node_modules/@sema/nucleo/dist/ir/modelos.d.ts +60 -0
  34. package/node_modules/@sema/nucleo/dist/lexer/tokens.js +15 -0
  35. package/node_modules/@sema/nucleo/dist/lexer/tokens.js.map +1 -1
  36. package/node_modules/@sema/nucleo/dist/parser/parser.js +98 -3
  37. package/node_modules/@sema/nucleo/dist/parser/parser.js.map +1 -1
  38. package/node_modules/@sema/nucleo/dist/persistencia/contratos.d.ts +39 -0
  39. package/node_modules/@sema/nucleo/dist/persistencia/contratos.js +294 -0
  40. package/node_modules/@sema/nucleo/dist/persistencia/contratos.js.map +1 -0
  41. package/node_modules/@sema/nucleo/dist/semantico/analisador.d.ts +1 -1
  42. package/node_modules/@sema/nucleo/dist/semantico/analisador.js +118 -2
  43. package/node_modules/@sema/nucleo/dist/semantico/analisador.js.map +1 -1
  44. package/node_modules/@sema/nucleo/package.json +1 -1
  45. package/node_modules/@sema/padroes/package.json +1 -1
  46. package/package.json +11 -11
package/dist/drift.js CHANGED
@@ -28,6 +28,340 @@ const DIRETORIOS_IGNORADOS = new Set([
28
28
  function normalizarFragmentoArquivo(valor) {
29
29
  return valor.replace(/\\/g, "/").replace(/^\.?\//, "").trim().toLowerCase();
30
30
  }
31
+ const NOMES_RECURSO_IGNORADOS = new Set([
32
+ "all",
33
+ "and",
34
+ "as",
35
+ "by",
36
+ "create",
37
+ "delete",
38
+ "from",
39
+ "group",
40
+ "inner",
41
+ "into",
42
+ "join",
43
+ "left",
44
+ "limit",
45
+ "offset",
46
+ "on",
47
+ "or",
48
+ "order",
49
+ "outer",
50
+ "returning",
51
+ "right",
52
+ "select",
53
+ "set",
54
+ "table",
55
+ "update",
56
+ "values",
57
+ "view",
58
+ "where",
59
+ ]);
60
+ const OPERACOES_REDIS_KEYSPACE = [
61
+ "append",
62
+ "decr",
63
+ "del",
64
+ "expire",
65
+ "expireat",
66
+ "get",
67
+ "getdel",
68
+ "getex",
69
+ "getrange",
70
+ "hdel",
71
+ "hexists",
72
+ "hget",
73
+ "hgetall",
74
+ "hincrby",
75
+ "hkeys",
76
+ "hlen",
77
+ "hmget",
78
+ "hmset",
79
+ "hrandfield",
80
+ "hscan",
81
+ "hset",
82
+ "hsetnx",
83
+ "hvals",
84
+ "incr",
85
+ "incrby",
86
+ "lindex",
87
+ "llen",
88
+ "lpop",
89
+ "lpush",
90
+ "lrange",
91
+ "lrem",
92
+ "lset",
93
+ "rpop",
94
+ "rpush",
95
+ "sadd",
96
+ "scard",
97
+ "set",
98
+ "setex",
99
+ "setnx",
100
+ "smembers",
101
+ "spop",
102
+ "srem",
103
+ "ttl",
104
+ "type",
105
+ "zadd",
106
+ "zcard",
107
+ "zrange",
108
+ "zrem",
109
+ ];
110
+ const OPERACOES_REDIS_STREAM = [
111
+ "xadd",
112
+ "xdel",
113
+ "xgroupcreate",
114
+ "xgroupdestroy",
115
+ "xlen",
116
+ "xrange",
117
+ "xread",
118
+ "xreadgroup",
119
+ "xrevrange",
120
+ "xtrim",
121
+ ];
122
+ function limparLiteralRecurso(valor) {
123
+ return valor
124
+ .trim()
125
+ .replace(/^["'`]+|["'`]+$/g, "")
126
+ .replace(/\$\{[^}]+\}/g, "")
127
+ .replace(/\{[^}]+\}/g, "")
128
+ .replace(/%[sdifjo]/gi, "")
129
+ .trim();
130
+ }
131
+ function fecharPrefixoRecurso(valor) {
132
+ return valor.replace(/[:/_\-.]+$/g, "").trim();
133
+ }
134
+ function normalizarNomeRecursoDrift(valor) {
135
+ return fecharPrefixoRecurso(limparLiteralRecurso(valor))
136
+ .normalize("NFD")
137
+ .replace(/[\u0300-\u036f]/g, "")
138
+ .toLowerCase()
139
+ .replace(/["'`]/g, "")
140
+ .replace(/\s+/g, "");
141
+ }
142
+ function variantesNomeRecursoDrift(valor) {
143
+ const base = fecharPrefixoRecurso(limparLiteralRecurso(valor));
144
+ if (!base) {
145
+ return [];
146
+ }
147
+ const variantes = new Set();
148
+ const registrar = (candidato) => {
149
+ if (!candidato) {
150
+ return;
151
+ }
152
+ const normalizado = normalizarNomeRecursoDrift(candidato);
153
+ if (normalizado) {
154
+ variantes.add(normalizado);
155
+ }
156
+ };
157
+ registrar(base);
158
+ registrar(base.replace(/[.:/_-]+/g, "_"));
159
+ registrar(base.replace(/[.:/_-]+/g, ""));
160
+ const partes = base.split(/[.:/_-]+/).filter(Boolean);
161
+ if (partes.length > 1) {
162
+ registrar(partes.join("_"));
163
+ registrar(partes.join(""));
164
+ }
165
+ const singular = base.replace(/s$/i, "");
166
+ if (singular && singular !== base) {
167
+ registrar(singular);
168
+ }
169
+ else if (!/s$/i.test(base)) {
170
+ registrar(`${base}s`);
171
+ }
172
+ return [...variantes];
173
+ }
174
+ function recursoEhIgnorado(nome) {
175
+ const normalizado = normalizarNomeRecursoDrift(nome);
176
+ if (!normalizado || normalizado.length < 2) {
177
+ return true;
178
+ }
179
+ return NOMES_RECURSO_IGNORADOS.has(normalizado);
180
+ }
181
+ function registrarRecursoDrift(recursos, origem, tipo, nome, arquivo, simbolo) {
182
+ const nomeLimpo = fecharPrefixoRecurso(limparLiteralRecurso(nome));
183
+ if (!nomeLimpo || recursoEhIgnorado(nomeLimpo)) {
184
+ return;
185
+ }
186
+ const chave = `${origem}:${tipo}:${normalizarNomeRecursoDrift(nomeLimpo)}:${arquivo}:${simbolo ?? ""}`;
187
+ if (!recursos.has(chave)) {
188
+ recursos.set(chave, {
189
+ origem,
190
+ nome: nomeLimpo,
191
+ arquivo,
192
+ simbolo,
193
+ tipo,
194
+ });
195
+ }
196
+ }
197
+ function inferirMotoresRelacionais(codigo, arquivo) {
198
+ const motores = new Set();
199
+ const caminho = normalizarFragmentoArquivo(arquivo);
200
+ if (/\b(?:from|require)\s*\(?["'`]pg["'`]/i.test(codigo)
201
+ || /\bpostgres(?:ql)?\b/i.test(codigo)
202
+ || /\bon\s+conflict\b/i.test(codigo)
203
+ || /\breturning\b/i.test(codigo)
204
+ || /\bjsonb\b/i.test(codigo)
205
+ || /\bilike\b/i.test(codigo)
206
+ || /(?:^|\/)(?:postgres|pgsql)(?:\/|[-_.])/i.test(caminho)) {
207
+ motores.add("postgres");
208
+ }
209
+ if (/\b(?:from|require)\s*\(?["'`](?:mysql2?(?:\/promise)?|mysql)["'`]/i.test(codigo)
210
+ || /\bon\s+duplicate\s+key\b/i.test(codigo)
211
+ || /\bauto_increment\b/i.test(codigo)
212
+ || /\binnodb\b/i.test(codigo)
213
+ || /\bunsigned\b/i.test(codigo)
214
+ || /(?:^|\/)mysql(?:\/|[-_.])/i.test(caminho)) {
215
+ motores.add("mysql");
216
+ }
217
+ if (/\b(?:from|require)\s*\(?["'`](?:sqlite3|better-sqlite3|bun:sqlite|sqlite)["'`]/i.test(codigo)
218
+ || /\bpragma\b/i.test(codigo)
219
+ || /\bwithout\s+rowid\b/i.test(codigo)
220
+ || /\bsqlite\b/i.test(codigo)
221
+ || /(?:^|\/)sqlite(?:\/|[-_.])/i.test(caminho)) {
222
+ motores.add("sqlite");
223
+ }
224
+ const temSqlGenerico = /\b(?:select\b[\s\S]*?\bfrom\b|insert\s+into|update\s+[A-Za-z_][\w$.-]*\s+set|delete\s+from|create\s+(?:table|view)|alter\s+table|drop\s+(?:table|view)|join\s+[A-Za-z_][\w$.-]*)/i.test(codigo)
225
+ || /\.(?:from|into|table)\s*\(\s*["'`]/i.test(codigo)
226
+ || /\b(?:knex|db|trx)\s*\(\s*["'`][A-Za-z_][^"'`]+["'`]\s*\)/i.test(codigo)
227
+ || /\bprisma\.[A-Za-z_]\w*\.(?:find\w+|create|update|delete|upsert|aggregate|count)\b/i.test(codigo);
228
+ if (temSqlGenerico && motores.size === 0) {
229
+ motores.add("postgres");
230
+ motores.add("mysql");
231
+ motores.add("sqlite");
232
+ }
233
+ return [...motores];
234
+ }
235
+ function extrairRecursosSql(arquivo, codigo) {
236
+ const recursos = new Map();
237
+ const motores = inferirMotoresRelacionais(codigo, arquivo);
238
+ if (motores.length === 0) {
239
+ return [];
240
+ }
241
+ const registrarParaMotores = (tipo, nome) => {
242
+ for (const motor of motores) {
243
+ registrarRecursoDrift(recursos, motor, tipo, nome, arquivo);
244
+ }
245
+ };
246
+ const registrarTextoSql = (texto) => {
247
+ if (!/\b(?:select\b[\s\S]*?\bfrom\b|insert\s+into|update\s+[A-Za-z_][\w$.-]*\s+set|delete\s+from|create\s+(?:table|view)|alter\s+table|drop\s+(?:table|view)|join\s+[A-Za-z_][\w$.-]*|create\s+(?:unique\s+)?index)\b/i.test(texto)) {
248
+ return;
249
+ }
250
+ for (const match of texto.matchAll(/\bcreate\s+(?:or\s+replace\s+)?(table|view)\s+(?:if\s+not\s+exists\s+)?["'`]?([A-Za-z_][\w$.-]*)["'`]?/gi)) {
251
+ registrarParaMotores(match[1].toLowerCase(), match[2]);
252
+ }
253
+ for (const match of texto.matchAll(/\bcreate\s+(?:unique\s+)?index\s+(?:if\s+not\s+exists\s+)?["'`]?([A-Za-z_][\w$.-]*)["'`]?/gi)) {
254
+ registrarParaMotores("index", match[1]);
255
+ }
256
+ for (const match of texto.matchAll(/\b(?:insert\s+into|update|from|join|delete\s+from|truncate\s+table)\s+["'`]?([A-Za-z_][\w$.-]*)["'`]?/gi)) {
257
+ registrarParaMotores("table", match[1]);
258
+ }
259
+ };
260
+ if (/\.(?:sql|psql|ddl)$/i.test(arquivo)) {
261
+ registrarTextoSql(codigo);
262
+ }
263
+ else {
264
+ for (const literal of codigo.matchAll(/(["'`])([\s\S]*?)\1/g)) {
265
+ registrarTextoSql(literal[2] ?? "");
266
+ }
267
+ }
268
+ for (const match of codigo.matchAll(/\.(?:from|into|table)\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/gi)) {
269
+ registrarParaMotores("table", match[1]);
270
+ }
271
+ for (const match of codigo.matchAll(/\b(?:knex|db|trx)\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/gi)) {
272
+ registrarParaMotores("table", match[1]);
273
+ }
274
+ for (const match of codigo.matchAll(/\bprisma\.([A-Za-z_]\w*)\.(?:find\w+|create|update|delete|upsert|aggregate|count)\b/gi)) {
275
+ registrarParaMotores("table", match[1]);
276
+ }
277
+ return [...recursos.values()];
278
+ }
279
+ function extrairRecursosMongoDb(arquivo, codigo) {
280
+ const recursos = new Map();
281
+ const contextoMongo = /\b(?:mongodb|mongoose|mongoclient|objectid)\b/i.test(codigo)
282
+ || /\bdb\.collection\s*\(/i.test(codigo)
283
+ || /(?:^|\/)mongo(?:db)?(?:\/|[-_.])/i.test(normalizarFragmentoArquivo(arquivo));
284
+ if (!contextoMongo) {
285
+ return [];
286
+ }
287
+ for (const match of codigo.matchAll(/\b(?:db\.)?collection\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/gi)) {
288
+ registrarRecursoDrift(recursos, "mongodb", "collection", match[1], arquivo);
289
+ }
290
+ for (const match of codigo.matchAll(/\bgetCollection\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/gi)) {
291
+ registrarRecursoDrift(recursos, "mongodb", "collection", match[1], arquivo);
292
+ }
293
+ for (const match of codigo.matchAll(/\bmongoose\.model\s*\(\s*["'`]([^"'`]+)["'`](?:\s*,[\s\S]*?,\s*["'`]([^"'`]+)["'`])?/gi)) {
294
+ registrarRecursoDrift(recursos, "mongodb", "document", match[1], arquivo);
295
+ if (match[2]) {
296
+ registrarRecursoDrift(recursos, "mongodb", "collection", match[2], arquivo);
297
+ }
298
+ }
299
+ for (const match of codigo.matchAll(/\bdb\.([A-Za-z_]\w*)\.(?:find|findOne|aggregate|insertOne|insertMany|updateOne|updateMany|deleteOne|deleteMany|countDocuments)\b/gi)) {
300
+ registrarRecursoDrift(recursos, "mongodb", "collection", match[1], arquivo);
301
+ }
302
+ return [...recursos.values()];
303
+ }
304
+ function extrairRecursosRedis(arquivo, codigo) {
305
+ const recursos = new Map();
306
+ const contextoRedis = /\b(?:from|require)\s*\(?["'`](?:redis|ioredis)["'`]/i.test(codigo)
307
+ || /\bcreateClient\s*\(/i.test(codigo)
308
+ || /\bx(?:add|read|readgroup|groupcreate|groupdestroy)\s*\(/i.test(codigo)
309
+ || /(?:^|\/)redis(?:\/|[-_.])/i.test(normalizarFragmentoArquivo(arquivo));
310
+ if (!contextoRedis) {
311
+ return [];
312
+ }
313
+ const operacoesKeyspace = OPERACOES_REDIS_KEYSPACE.join("|");
314
+ const operacoesStream = OPERACOES_REDIS_STREAM.join("|");
315
+ const padraoKeyspace = new RegExp(`\\b(?:${operacoesKeyspace})\\s*\\(\\s*['"\\\`]([^'"\\\`]+)['"\\\`]`, "gi");
316
+ const padraoStream = new RegExp(`\\b(?:${operacoesStream})\\s*\\(\\s*['"\\\`]([^'"\\\`]+)['"\\\`]`, "gi");
317
+ for (const match of codigo.matchAll(padraoKeyspace)) {
318
+ registrarRecursoDrift(recursos, "redis", "keyspace", match[1], arquivo);
319
+ }
320
+ for (const match of codigo.matchAll(padraoStream)) {
321
+ registrarRecursoDrift(recursos, "redis", "stream", match[1], arquivo);
322
+ }
323
+ return [...recursos.values()];
324
+ }
325
+ function extrairRecursosPersistenciaCodigoVivo(arquivo, codigo) {
326
+ const recursos = new Map();
327
+ for (const recurso of extrairColecoesFirebase(arquivo, codigo)) {
328
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
329
+ }
330
+ for (const recurso of extrairRecursosSql(arquivo, codigo)) {
331
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
332
+ }
333
+ for (const recurso of extrairRecursosMongoDb(arquivo, codigo)) {
334
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
335
+ }
336
+ for (const recurso of extrairRecursosRedis(arquivo, codigo)) {
337
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
338
+ }
339
+ return [...recursos.values()];
340
+ }
341
+ function extrairRecursosPrisma(arquivo, codigo) {
342
+ const recursos = new Map();
343
+ const provider = codigo.match(/\bprovider\s*=\s*["'`](postgresql|mysql|sqlite)["'`]/i)?.[1]?.toLowerCase();
344
+ const origem = provider === "postgresql"
345
+ ? "postgres"
346
+ : provider === "mysql"
347
+ ? "mysql"
348
+ : provider === "sqlite"
349
+ ? "sqlite"
350
+ : undefined;
351
+ if (!origem) {
352
+ return [];
353
+ }
354
+ for (const match of codigo.matchAll(/\bmodel\s+([A-Za-z_]\w*)\s*\{([\s\S]*?)\n\}/g)) {
355
+ const nomeModelo = match[1];
356
+ const corpo = match[2] ?? "";
357
+ const tabelaMapeada = corpo.match(/@@map\s*\(\s*["'`]([^"'`]+)["'`]\s*\)/)?.[1];
358
+ registrarRecursoDrift(recursos, origem, "table", tabelaMapeada ?? nomeModelo, arquivo);
359
+ if (tabelaMapeada) {
360
+ registrarRecursoDrift(recursos, origem, "table", nomeModelo, arquivo);
361
+ }
362
+ }
363
+ return [...recursos.values()];
364
+ }
31
365
  function escolherArquivoPorVinculo(arquivos, valor) {
32
366
  const normalizado = normalizarFragmentoArquivo(valor);
33
367
  const exato = arquivos.find((arquivo) => normalizarFragmentoArquivo(arquivo) === normalizado);
@@ -701,8 +1035,8 @@ async function indexarTypeScript(diretorios) {
701
1035
  const sourceFile = ts.createSourceFile(arquivo, codigo, ts.ScriptTarget.Latest, true, scriptKind);
702
1036
  const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
703
1037
  const relacao = path.relative(diretorio, arquivo);
704
- for (const recurso of extrairColecoesFirebase(arquivo, codigo)) {
705
- recursos.set(`${recurso.nome}:${recurso.arquivo}:${recurso.tipo}`, recurso);
1038
+ for (const recurso of extrairRecursosPersistenciaCodigoVivo(arquivo, codigo)) {
1039
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
706
1040
  }
707
1041
  for (const rota of extrairRotasTypeScriptHttp(sourceFile, relacao)) {
708
1042
  rotas.push({
@@ -889,6 +1223,7 @@ function registrarRotasPython(rotas, decoratorsPendentes, prefixo, arquivo, nome
889
1223
  async function indexarPython(diretorios) {
890
1224
  const simbolos = new Map();
891
1225
  const rotas = [];
1226
+ const recursos = new Map();
892
1227
  for (const diretorio of diretorios) {
893
1228
  const arquivos = (await listarArquivosRecursivos(diretorio, [".py"]))
894
1229
  .filter((arquivo) => !arquivo.endsWith("__init__.py") && !/tests?[\\/]/i.test(arquivo));
@@ -896,6 +1231,9 @@ async function indexarPython(diretorios) {
896
1231
  const texto = await readFile(arquivo, "utf8");
897
1232
  const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
898
1233
  const prefixo = texto.match(/APIRouter\s*\(\s*prefix\s*=\s*["']([^"']+)["']/)?.[1];
1234
+ for (const recurso of extrairRecursosPersistenciaCodigoVivo(arquivo, texto)) {
1235
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
1236
+ }
899
1237
  for (const rota of extrairRotasFlaskDecoradas(texto)) {
900
1238
  rotas.push({
901
1239
  origem: "flask",
@@ -947,11 +1285,12 @@ async function indexarPython(diretorios) {
947
1285
  }
948
1286
  }
949
1287
  }
950
- return { simbolos: [...simbolos.values()], rotas };
1288
+ return { simbolos: [...simbolos.values()], rotas, recursos: [...recursos.values()] };
951
1289
  }
952
1290
  async function indexarDart(diretorios) {
953
1291
  const simbolos = new Map();
954
1292
  const rotas = [];
1293
+ const recursos = new Map();
955
1294
  const consumerSurfaces = new Map();
956
1295
  for (const diretorio of diretorios) {
957
1296
  const arquivos = (await listarArquivosRecursivos(diretorio, [".dart"]))
@@ -960,6 +1299,9 @@ async function indexarDart(diretorios) {
960
1299
  const texto = await readFile(arquivo, "utf8");
961
1300
  const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
962
1301
  const relacao = path.relative(diretorio, arquivo);
1302
+ for (const recurso of extrairRecursosPersistenciaCodigoVivo(arquivo, texto)) {
1303
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
1304
+ }
963
1305
  for (const match of texto.matchAll(/(?:Future<[^\n]+>|[\w?<>.,\s]+)\s+(\w+)\(([^)]*)\)\s*(?:async\s*)?\{/g)) {
964
1306
  const nome = match[1];
965
1307
  if (["build", "toString"].includes(nome)) {
@@ -1006,6 +1348,7 @@ async function indexarDart(diretorios) {
1006
1348
  return {
1007
1349
  simbolos: [...simbolos.values()],
1008
1350
  rotas,
1351
+ recursos: [...recursos.values()],
1009
1352
  consumerSurfaces: [...consumerSurfaces.values()].sort((a, b) => a.rota.localeCompare(b.rota, "pt-BR")
1010
1353
  || a.tipoArquivo.localeCompare(b.tipoArquivo, "pt-BR")
1011
1354
  || a.arquivo.localeCompare(b.arquivo, "pt-BR")),
@@ -1037,12 +1380,16 @@ function registrarSimboloGenerico(simbolos, origem, basesSimbolicas, arquivo, si
1037
1380
  async function indexarDotnet(diretorios) {
1038
1381
  const simbolos = new Map();
1039
1382
  const rotas = [];
1383
+ const recursos = new Map();
1040
1384
  for (const diretorio of diretorios) {
1041
1385
  const arquivos = (await listarArquivosRecursivos(diretorio, [".cs"]))
1042
1386
  .filter((arquivo) => !/(^|[\\/])(bin|obj|Test[s]?)([\\/]|$)/i.test(arquivo));
1043
1387
  for (const arquivo of arquivos) {
1044
1388
  const codigo = await readFile(arquivo, "utf8");
1045
1389
  const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
1390
+ for (const recurso of extrairRecursosPersistenciaCodigoVivo(arquivo, codigo)) {
1391
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
1392
+ }
1046
1393
  for (const simbolo of extrairSimbolosDotnet(codigo)) {
1047
1394
  registrarSimboloGenerico(simbolos, "cs", basesSimbolicas, arquivo, simbolo.simbolo);
1048
1395
  }
@@ -1057,17 +1404,21 @@ async function indexarDotnet(diretorios) {
1057
1404
  }
1058
1405
  }
1059
1406
  }
1060
- return { simbolos: [...simbolos.values()], rotas };
1407
+ return { simbolos: [...simbolos.values()], rotas, recursos: [...recursos.values()] };
1061
1408
  }
1062
1409
  async function indexarJava(diretorios) {
1063
1410
  const simbolos = new Map();
1064
1411
  const rotas = [];
1412
+ const recursos = new Map();
1065
1413
  for (const diretorio of diretorios) {
1066
1414
  const arquivos = (await listarArquivosRecursivos(diretorio, [".java"]))
1067
1415
  .filter((arquivo) => !/(^|[\\/])(target|build|out|Test[s]?)([\\/]|$)/i.test(arquivo));
1068
1416
  for (const arquivo of arquivos) {
1069
1417
  const codigo = await readFile(arquivo, "utf8");
1070
1418
  const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
1419
+ for (const recurso of extrairRecursosPersistenciaCodigoVivo(arquivo, codigo)) {
1420
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
1421
+ }
1071
1422
  for (const simbolo of extrairSimbolosJava(codigo)) {
1072
1423
  registrarSimboloGenerico(simbolos, "java", basesSimbolicas, arquivo, simbolo.simbolo);
1073
1424
  }
@@ -1082,16 +1433,20 @@ async function indexarJava(diretorios) {
1082
1433
  }
1083
1434
  }
1084
1435
  }
1085
- return { simbolos: [...simbolos.values()], rotas };
1436
+ return { simbolos: [...simbolos.values()], rotas, recursos: [...recursos.values()] };
1086
1437
  }
1087
1438
  async function indexarGo(diretorios) {
1088
1439
  const simbolos = new Map();
1089
1440
  const rotas = [];
1441
+ const recursos = new Map();
1090
1442
  for (const diretorio of diretorios) {
1091
1443
  const arquivos = await listarArquivosRecursivos(diretorio, [".go"]);
1092
1444
  for (const arquivo of arquivos) {
1093
1445
  const codigo = await readFile(arquivo, "utf8");
1094
1446
  const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
1447
+ for (const recurso of extrairRecursosPersistenciaCodigoVivo(arquivo, codigo)) {
1448
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
1449
+ }
1095
1450
  for (const simbolo of extrairSimbolosGo(codigo)) {
1096
1451
  registrarSimboloGenerico(simbolos, "go", basesSimbolicas, arquivo, simbolo.simbolo);
1097
1452
  }
@@ -1106,16 +1461,20 @@ async function indexarGo(diretorios) {
1106
1461
  }
1107
1462
  }
1108
1463
  }
1109
- return { simbolos: [...simbolos.values()], rotas };
1464
+ return { simbolos: [...simbolos.values()], rotas, recursos: [...recursos.values()] };
1110
1465
  }
1111
1466
  async function indexarRust(diretorios) {
1112
1467
  const simbolos = new Map();
1113
1468
  const rotas = [];
1469
+ const recursos = new Map();
1114
1470
  for (const diretorio of diretorios) {
1115
1471
  const arquivos = await listarArquivosRecursivos(diretorio, [".rs"]);
1116
1472
  for (const arquivo of arquivos) {
1117
1473
  const codigo = await readFile(arquivo, "utf8");
1118
1474
  const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
1475
+ for (const recurso of extrairRecursosPersistenciaCodigoVivo(arquivo, codigo)) {
1476
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
1477
+ }
1119
1478
  for (const simbolo of extrairSimbolosRust(codigo)) {
1120
1479
  registrarSimboloGenerico(simbolos, "rust", basesSimbolicas, arquivo, simbolo.simbolo);
1121
1480
  }
@@ -1130,22 +1489,45 @@ async function indexarRust(diretorios) {
1130
1489
  }
1131
1490
  }
1132
1491
  }
1133
- return { simbolos: [...simbolos.values()], rotas };
1492
+ return { simbolos: [...simbolos.values()], rotas, recursos: [...recursos.values()] };
1134
1493
  }
1135
1494
  async function indexarCpp(diretorios) {
1136
1495
  const simbolos = new Map();
1496
+ const recursos = new Map();
1137
1497
  for (const diretorio of diretorios) {
1138
1498
  const arquivos = (await listarArquivosRecursivos(diretorio, [".cpp", ".cc", ".cxx", ".hpp", ".h"]))
1139
1499
  .filter((arquivo) => !/(^|[\\/])(windows|linux|macos|runner|flutter|ephemeral|build|vendor)([\\/]|$)/i.test(arquivo));
1140
1500
  for (const arquivo of arquivos) {
1141
1501
  const codigo = await readFile(arquivo, "utf8");
1142
1502
  const basesSimbolicas = caminhosSimbolicos(diretorio, arquivo);
1503
+ for (const recurso of extrairRecursosPersistenciaCodigoVivo(arquivo, codigo)) {
1504
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
1505
+ }
1143
1506
  for (const simbolo of extrairSimbolosCpp(codigo)) {
1144
1507
  registrarSimboloGenerico(simbolos, "cpp", basesSimbolicas, arquivo, simbolo.simbolo);
1145
1508
  }
1146
1509
  }
1147
1510
  }
1148
- return [...simbolos.values()];
1511
+ return {
1512
+ simbolos: [...simbolos.values()],
1513
+ recursos: [...recursos.values()],
1514
+ };
1515
+ }
1516
+ async function indexarPersistenciaDeclarativa(diretorios) {
1517
+ const recursos = new Map();
1518
+ for (const diretorio of diretorios) {
1519
+ const arquivos = await listarArquivosRecursivos(diretorio, [".sql", ".psql", ".ddl", ".prisma"]);
1520
+ for (const arquivo of arquivos) {
1521
+ const codigo = await readFile(arquivo, "utf8");
1522
+ const extracoes = arquivo.endsWith(".prisma")
1523
+ ? extrairRecursosPrisma(arquivo, codigo)
1524
+ : extrairRecursosPersistenciaCodigoVivo(arquivo, codigo);
1525
+ for (const recurso of extracoes) {
1526
+ registrarRecursoDrift(recursos, recurso.origem, recurso.tipo, recurso.nome, recurso.arquivo, recurso.simbolo);
1527
+ }
1528
+ }
1529
+ }
1530
+ return { recursos: [...recursos.values()] };
1149
1531
  }
1150
1532
  function normalizarCaminhoRota(caminho) {
1151
1533
  if (!caminho) {
@@ -1337,16 +1719,121 @@ function escolherRotasEsperadas(task, fontesLegado) {
1337
1719
  function taskEhBridgeFirebase(task) {
1338
1720
  return task.implementacoesExternas.some((impl) => impl.origem === "ts" && /sema_contract_bridge|collections?|apps\.worker/i.test(impl.caminho));
1339
1721
  }
1340
- function extrairRecursosEsperados(task) {
1341
- if (!taskEhBridgeFirebase(task)) {
1342
- return [];
1722
+ function tiposAceitosParaRecursoPersistencia(recurso) {
1723
+ switch (recurso.resourceKind) {
1724
+ case "table":
1725
+ case "view":
1726
+ case "query":
1727
+ case "index":
1728
+ case "collection":
1729
+ case "document":
1730
+ case "keyspace":
1731
+ case "stream":
1732
+ return [recurso.resourceKind];
1733
+ default:
1734
+ return [];
1735
+ }
1736
+ }
1737
+ function nomesRecursoPersistencia(recurso) {
1738
+ return [...new Set([
1739
+ recurso.nome,
1740
+ recurso.table,
1741
+ recurso.collection,
1742
+ recurso.entity,
1743
+ recurso.path,
1744
+ recurso.surface,
1745
+ ].filter((item) => Boolean(item)))];
1746
+ }
1747
+ function recursoPersistenciaCombinaAlvo(recurso, alvo) {
1748
+ const alvoVariantes = new Set(variantesNomeRecursoDrift(alvo));
1749
+ if (alvoVariantes.size === 0) {
1750
+ return false;
1751
+ }
1752
+ return nomesRecursoPersistencia(recurso).some((nome) => variantesNomeRecursoDrift(nome).some((variacao) => alvoVariantes.has(variacao)));
1753
+ }
1754
+ function extrairRecursosEsperados(task, ir) {
1755
+ const esperados = new Map();
1756
+ const registrar = (esperado) => {
1757
+ const chave = `${esperado.origem ?? "qualquer"}:${esperado.tiposAceitos.join(",")}:${esperado.nomes.join("|")}:${esperado.alvo}`;
1758
+ if (!esperados.has(chave)) {
1759
+ esperados.set(chave, esperado);
1760
+ }
1761
+ };
1762
+ if (taskEhBridgeFirebase(task)) {
1763
+ for (const efeito of task.efeitosEstruturados.filter((item) => item.categoria === "persistencia" && Boolean(item.alvo))) {
1764
+ registrar({
1765
+ categoria: "persistencia",
1766
+ alvo: efeito.alvo,
1767
+ origem: "firebase",
1768
+ tiposAceitos: ["colecao"],
1769
+ nomes: [efeito.alvo],
1770
+ });
1771
+ }
1772
+ }
1773
+ const efeitosPersistencia = task.efeitosEstruturados.filter((efeito) => ["persistencia", "db.read", "db.write"].includes(efeito.categoria) && Boolean(efeito.alvo));
1774
+ if (efeitosPersistencia.length === 0 || ir.databases.length === 0) {
1775
+ return [...esperados.values()];
1343
1776
  }
1344
- return task.efeitosEstruturados
1345
- .filter((efeito) => efeito.categoria === "persistencia" && Boolean(efeito.alvo))
1346
- .map((efeito) => ({
1347
- categoria: "persistencia",
1348
- alvo: efeito.alvo,
1349
- }));
1777
+ for (const efeito of efeitosPersistencia) {
1778
+ for (const database of ir.databases) {
1779
+ for (const recurso of database.resources) {
1780
+ const tiposAceitos = tiposAceitosParaRecursoPersistencia(recurso);
1781
+ if (tiposAceitos.length === 0 || !recursoPersistenciaCombinaAlvo(recurso, efeito.alvo)) {
1782
+ continue;
1783
+ }
1784
+ registrar({
1785
+ categoria: "persistencia",
1786
+ alvo: efeito.alvo,
1787
+ origem: database.engine,
1788
+ tiposAceitos,
1789
+ nomes: nomesRecursoPersistencia(recurso),
1790
+ });
1791
+ }
1792
+ }
1793
+ }
1794
+ return [...esperados.values()];
1795
+ }
1796
+ function construirMapaRecursos(recursos) {
1797
+ const mapa = new Map();
1798
+ for (const recurso of recursos) {
1799
+ for (const variante of variantesNomeRecursoDrift(recurso.nome)) {
1800
+ const existentes = mapa.get(variante) ?? [];
1801
+ if (!existentes.some((item) => item.origem === recurso.origem
1802
+ && item.tipo === recurso.tipo
1803
+ && item.arquivo === recurso.arquivo
1804
+ && item.nome === recurso.nome
1805
+ && item.simbolo === recurso.simbolo)) {
1806
+ existentes.push(recurso);
1807
+ mapa.set(variante, existentes);
1808
+ }
1809
+ }
1810
+ }
1811
+ return mapa;
1812
+ }
1813
+ function recursoResolvidoCombinaEsperado(recurso, esperado) {
1814
+ if (esperado.origem && recurso.origem !== esperado.origem) {
1815
+ return false;
1816
+ }
1817
+ if (esperado.tiposAceitos.length > 0 && !esperado.tiposAceitos.includes(recurso.tipo)) {
1818
+ return false;
1819
+ }
1820
+ const recursoVariantes = new Set(variantesNomeRecursoDrift(recurso.nome));
1821
+ return esperado.nomes.some((nome) => variantesNomeRecursoDrift(nome).some((variante) => recursoVariantes.has(variante)));
1822
+ }
1823
+ function resolverRecursoEsperado(mapaRecursos, esperado, arquivosPreferidos) {
1824
+ const candidatos = new Map();
1825
+ for (const nome of esperado.nomes) {
1826
+ for (const variante of variantesNomeRecursoDrift(nome)) {
1827
+ for (const recurso of mapaRecursos.get(variante) ?? []) {
1828
+ if (recursoResolvidoCombinaEsperado(recurso, esperado)) {
1829
+ candidatos.set(`${recurso.origem}:${recurso.tipo}:${recurso.nome}:${recurso.arquivo}:${recurso.simbolo ?? ""}`, recurso);
1830
+ }
1831
+ }
1832
+ }
1833
+ }
1834
+ return [...candidatos.values()].sort((a, b) => Number(Boolean(arquivosPreferidos?.has(b.arquivo))) - Number(Boolean(arquivosPreferidos?.has(a.arquivo)))
1835
+ || a.arquivo.localeCompare(b.arquivo, "pt-BR")
1836
+ || a.nome.localeCompare(b.nome, "pt-BR"))[0];
1350
1837
  }
1351
1838
  function coletarVinculosIr(ir) {
1352
1839
  return [
@@ -1365,6 +1852,7 @@ export async function analisarDriftLegado(contexto) {
1365
1852
  const indexJava = await indexarJava(contexto.diretoriosCodigo);
1366
1853
  const indexGo = await indexarGo(contexto.diretoriosCodigo);
1367
1854
  const indexRust = await indexarRust(contexto.diretoriosCodigo);
1855
+ const indexPersistencia = await indexarPersistenciaDeclarativa(contexto.diretoriosCodigo);
1368
1856
  const indexCpp = await indexarCpp(contexto.diretoriosCodigo);
1369
1857
  const todosSimbolos = [
1370
1858
  ...indexTs.simbolos,
@@ -1374,7 +1862,7 @@ export async function analisarDriftLegado(contexto) {
1374
1862
  ...indexJava.simbolos,
1375
1863
  ...indexGo.simbolos,
1376
1864
  ...indexRust.simbolos,
1377
- ...indexCpp,
1865
+ ...indexCpp.simbolos,
1378
1866
  ];
1379
1867
  const mapaImpl = new Map([
1380
1868
  ...indexTs.simbolos.map((item) => [item.caminho, item]),
@@ -1384,9 +1872,20 @@ export async function analisarDriftLegado(contexto) {
1384
1872
  ...indexJava.simbolos.map((item) => [item.caminho, item]),
1385
1873
  ...indexGo.simbolos.map((item) => [item.caminho, item]),
1386
1874
  ...indexRust.simbolos.map((item) => [item.caminho, item]),
1387
- ...indexCpp.map((item) => [item.caminho, item]),
1875
+ ...indexCpp.simbolos.map((item) => [item.caminho, item]),
1388
1876
  ]);
1389
- const mapaRecursos = new Map(indexTs.recursos.map((item) => [item.nome, item]));
1877
+ const todosRecursos = [
1878
+ ...indexTs.recursos,
1879
+ ...indexPy.recursos,
1880
+ ...indexDart.recursos,
1881
+ ...indexDotnet.recursos,
1882
+ ...indexJava.recursos,
1883
+ ...indexGo.recursos,
1884
+ ...indexRust.recursos,
1885
+ ...indexCpp.recursos,
1886
+ ...indexPersistencia.recursos,
1887
+ ];
1888
+ const mapaRecursos = construirMapaRecursos(todosRecursos);
1390
1889
  const todasRotasIndexadas = [
1391
1890
  ...indexTs.rotas,
1392
1891
  ...indexPy.rotas,
@@ -1399,7 +1898,7 @@ export async function analisarDriftLegado(contexto) {
1399
1898
  const todosArquivosConhecidos = [...new Set([
1400
1899
  ...todosSimbolos.map((item) => item.arquivo),
1401
1900
  ...todasRotasIndexadas.map((item) => item.arquivo),
1402
- ...indexTs.recursos.map((item) => item.arquivo),
1901
+ ...todosRecursos.map((item) => item.arquivo),
1403
1902
  ])].sort((a, b) => a.localeCompare(b, "pt-BR"));
1404
1903
  const implsValidos = [];
1405
1904
  const implsQuebrados = [];
@@ -1550,16 +2049,16 @@ export async function analisarDriftLegado(contexto) {
1550
2049
  candidatosImpl: ordenarCandidatos([...candidatosTask.values()]).slice(0, 5),
1551
2050
  checksSugeridos: [],
1552
2051
  });
1553
- for (const recursoEsperado of extrairRecursosEsperados(task)) {
1554
- const resolvido = mapaRecursos.get(recursoEsperado.alvo);
2052
+ for (const recursoEsperado of extrairRecursosEsperados(task, ir)) {
2053
+ const resolvido = resolverRecursoEsperado(mapaRecursos, recursoEsperado, arquivosReferenciados);
1555
2054
  const registro = {
1556
2055
  modulo: ir.nome,
1557
2056
  task: task.nome,
1558
2057
  categoria: recursoEsperado.categoria,
1559
2058
  alvo: recursoEsperado.alvo,
1560
2059
  arquivo: resolvido?.arquivo ?? "",
1561
- origem: "firebase",
1562
- tipo: "colecao",
2060
+ origem: resolvido?.origem ?? recursoEsperado.origem ?? "firebase",
2061
+ tipo: resolvido?.tipo ?? recursoEsperado.tiposAceitos[0] ?? "query",
1563
2062
  status: resolvido ? "resolvido" : "divergente",
1564
2063
  };
1565
2064
  if (resolvido) {
@@ -1568,11 +2067,12 @@ export async function analisarDriftLegado(contexto) {
1568
2067
  }
1569
2068
  else {
1570
2069
  recursosDivergentes.push(registro);
2070
+ const escopo = recursoEsperado.origem ? `${recursoEsperado.origem}` : "persistencia declarada";
1571
2071
  diagnosticos.push({
1572
2072
  tipo: "recurso_divergente",
1573
2073
  modulo: ir.nome,
1574
2074
  task: task.nome,
1575
- mensagem: `Recurso vivo "${recursoEsperado.alvo}" nao foi encontrado nos bridges/configuracoes Firebase do codigo legado.`,
2075
+ mensagem: `Recurso vivo "${recursoEsperado.alvo}" nao foi encontrado no codigo legado para ${escopo}.`,
1576
2076
  });
1577
2077
  }
1578
2078
  }
@@ -1689,7 +2189,12 @@ export async function analisarDriftLegado(contexto) {
1689
2189
  registro.arquivo = resolucaoArquivo.arquivo;
1690
2190
  }
1691
2191
  else if (recursoDeclarado) {
1692
- const recurso = mapaRecursos.get(recursoDeclarado);
2192
+ const recurso = resolverRecursoEsperado(mapaRecursos, {
2193
+ categoria: "persistencia",
2194
+ alvo: recursoDeclarado,
2195
+ tiposAceitos: [],
2196
+ nomes: [recursoDeclarado],
2197
+ });
1693
2198
  if (recurso) {
1694
2199
  registro.status = "resolvido";
1695
2200
  registro.confianca = "alta";