@justmpm/ai-tool 0.9.3 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -143,9 +143,263 @@ function isCodeFile(filePath) {
143
143
  return CODE_EXTENSIONS.some((ext) => filePath.endsWith(ext));
144
144
  }
145
145
 
146
+ // src/utils/hints.ts
147
+ var COMMAND_MAP = {
148
+ map: {
149
+ cli: "ai-tool map",
150
+ mcp: "analyze__aitool_project_map"
151
+ },
152
+ dead: {
153
+ cli: "ai-tool dead",
154
+ mcp: "analyze__aitool_dead_code"
155
+ },
156
+ impact: {
157
+ cli: "ai-tool impact <arquivo>",
158
+ mcp: "analyze__aitool_impact_analysis { target: '<arquivo>' }"
159
+ },
160
+ suggest: {
161
+ cli: "ai-tool suggest <arquivo>",
162
+ mcp: "analyze__aitool_suggest_reads { target: '<arquivo>' }"
163
+ },
164
+ context: {
165
+ cli: "ai-tool context <arquivo>",
166
+ mcp: "analyze__aitool_file_context { target: '<arquivo>' }"
167
+ },
168
+ area_context: {
169
+ cli: "ai-tool context --area=<nome>",
170
+ mcp: "analyze__aitool_area_context { area: '<nome>' }"
171
+ },
172
+ areas: {
173
+ cli: "ai-tool areas",
174
+ mcp: "analyze__aitool_list_areas"
175
+ },
176
+ area: {
177
+ cli: "ai-tool area <nome>",
178
+ mcp: "analyze__aitool_area_detail { target: '<nome>' }"
179
+ },
180
+ areas_init: {
181
+ cli: "ai-tool areas init",
182
+ mcp: "analyze__aitool_areas_init"
183
+ },
184
+ find: {
185
+ cli: "ai-tool find <termo>",
186
+ mcp: "analyze__aitool_find { query: '<termo>' }"
187
+ },
188
+ describe: {
189
+ cli: "ai-tool describe <termo>",
190
+ mcp: "analyze__aitool_describe { query: '<termo>' }"
191
+ },
192
+ functions: {
193
+ cli: "ai-tool functions",
194
+ mcp: "analyze__aitool_list_functions"
195
+ }
196
+ };
197
+ function hint(command, ctx, params) {
198
+ const mapping = COMMAND_MAP[command];
199
+ if (!mapping) return command;
200
+ let instruction = mapping[ctx];
201
+ if (params) {
202
+ for (const [placeholder, value] of Object.entries(params)) {
203
+ instruction = instruction.replaceAll(placeholder, value);
204
+ }
205
+ }
206
+ return instruction;
207
+ }
208
+ var NEXT_STEPS = {
209
+ map: [
210
+ { command: "area", description: "ver arquivos de uma area" },
211
+ { command: "suggest", description: "o que ler antes de editar" },
212
+ { command: "context", description: "ver API de um arquivo" }
213
+ ],
214
+ impact: [
215
+ { command: "suggest", description: "o que ler antes de editar este arquivo" },
216
+ { command: "context", description: "ver assinaturas dos arquivos upstream" },
217
+ { command: "find", description: "localizar usos de exports especificos" }
218
+ ],
219
+ suggest: [
220
+ { command: "context", description: "ver assinaturas de cada arquivo sugerido" },
221
+ { command: "impact", description: "ver impacto completo da mudanca" }
222
+ ],
223
+ context: [
224
+ { command: "impact", description: "ver quem sera afetado por mudancas" },
225
+ { command: "find", description: "localizar usos de uma funcao ou tipo" },
226
+ { command: "suggest", description: "o que ler antes de editar" }
227
+ ],
228
+ area_context: [
229
+ { command: "find", description: "buscar usos de simbolos desta area" },
230
+ { command: "area", description: "ver lista de arquivos desta area" },
231
+ { command: "impact", description: "ver impacto de modificar um arquivo" }
232
+ ],
233
+ find: [
234
+ { command: "context", description: "ver assinaturas completas do arquivo" },
235
+ { command: "impact", description: "ver impacto de modificar o arquivo" },
236
+ { command: "describe", description: "buscar areas por descricao" }
237
+ ],
238
+ describe: [
239
+ { command: "area", description: "ver detalhes de uma area" },
240
+ { command: "area_context", description: "contexto completo de uma area" }
241
+ ],
242
+ areas: [
243
+ { command: "area", description: "ver arquivos de uma area especifica" },
244
+ { command: "describe", description: "buscar areas por descricao" }
245
+ ],
246
+ area: [
247
+ { command: "area_context", description: "contexto consolidado (tipos, hooks, funcoes)" },
248
+ { command: "context", description: "ver assinaturas de um arquivo especifico" },
249
+ { command: "find", description: "buscar simbolos dentro da area" }
250
+ ],
251
+ dead: [
252
+ { command: "impact", description: "verificar se um arquivo morto e realmente nao usado" },
253
+ { command: "map", description: "ver estrutura atualizada do projeto" }
254
+ ],
255
+ functions: [
256
+ { command: "find", description: "buscar uma Cloud Function especifica" },
257
+ { command: "impact", description: "ver impacto de modificar uma function" },
258
+ { command: "context", description: "ver assinaturas de uma function" }
259
+ ]
260
+ };
261
+ function nextSteps(command, ctx) {
262
+ const steps = NEXT_STEPS[command];
263
+ if (!steps || steps.length === 0) return "";
264
+ let out = "\n\u{1F4D6} Proximos passos:\n";
265
+ for (const step of steps) {
266
+ const instruction = hint(step.command, ctx);
267
+ out += ` \u2192 ${instruction} - ${step.description}
268
+ `;
269
+ }
270
+ return out;
271
+ }
272
+ function recoveryHint(errorType, ctx, _extra) {
273
+ switch (errorType) {
274
+ case "file_not_found":
275
+ return `
276
+ \u{1F4A1} Dicas:
277
+ \u2192 ${hint("map", ctx)} - ver arquivos disponiveis
278
+ \u2192 ${hint("find", ctx)} - buscar por nome de simbolo
279
+ `;
280
+ case "area_not_found":
281
+ return `
282
+ \u{1F4A1} Dicas:
283
+ \u2192 ${hint("areas", ctx)} - listar areas disponiveis
284
+ \u2192 ${hint("describe", ctx)} - buscar areas por descricao
285
+ \u2192 ${hint("areas_init", ctx)} - gerar configuracao de areas
286
+ `;
287
+ case "no_results":
288
+ return `
289
+ \u{1F4A1} Dicas:
290
+ \u2192 ${hint("find", ctx)} - buscar com outro termo
291
+ \u2192 ${hint("describe", ctx)} - buscar areas por descricao
292
+ \u2192 ${hint("map", ctx)} - ver estrutura do projeto
293
+ `;
294
+ case "no_firebase":
295
+ return `
296
+ \u{1F4A1} Este projeto nao usa Firebase. Comandos disponiveis:
297
+ \u2192 ${hint("map", ctx)} - ver estrutura do projeto
298
+ \u2192 ${hint("find", ctx)} - buscar simbolos no codigo
299
+ \u2192 ${hint("areas", ctx)} - listar areas funcionais
300
+ `;
301
+ case "no_areas_configured":
302
+ return `
303
+ \u{1F4A1} Nenhuma area configurada neste projeto.
304
+ \u2192 ${hint("areas_init", ctx)} - gerar arquivo de configuracao
305
+ \u2192 Depois edite .analyze/areas.config.json com as areas do projeto
306
+ \u2192 ${hint("map", ctx)} - ver estrutura do projeto sem areas
307
+ `;
308
+ case "symbol_not_found":
309
+ return `
310
+ \u{1F4A1} Dicas:
311
+ \u2192 Verifique a ortografia do simbolo
312
+ \u2192 Tente buscar parte do nome
313
+ \u2192 ${hint("find", ctx)} - buscar com outro termo
314
+ \u2192 ${hint("describe", ctx)} - buscar areas por descricao
315
+ \u2192 ${hint("map", ctx)} - ver estrutura do projeto
316
+ `;
317
+ case "index_failed":
318
+ return `
319
+ \u{1F4A1} Falha ao indexar o projeto:
320
+ \u2192 Verifique se tsconfig.json existe e esta valido
321
+ \u2192 Verifique se o projeto tem arquivos .ts ou .tsx
322
+ \u2192 ${hint("map", ctx)} - tente ver a estrutura basica primeiro
323
+ `;
324
+ case "generic":
325
+ return `
326
+ \u{1F4A1} Tente:
327
+ \u2192 ${hint("map", ctx)} - verificar estrutura do projeto
328
+ \u2192 Verifique se o caminho (cwd) esta correto
329
+ `;
330
+ }
331
+ }
332
+
333
+ // src/utils/similarity.ts
334
+ function levenshteinDistance(a, b) {
335
+ const matrix = [];
336
+ for (let i = 0; i <= b.length; i++) {
337
+ matrix[i] = [i];
338
+ }
339
+ for (let j = 0; j <= a.length; j++) {
340
+ matrix[0][j] = j;
341
+ }
342
+ for (let i = 1; i <= b.length; i++) {
343
+ for (let j = 1; j <= a.length; j++) {
344
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
345
+ matrix[i][j] = matrix[i - 1][j - 1];
346
+ } else {
347
+ matrix[i][j] = Math.min(
348
+ matrix[i - 1][j - 1] + 1,
349
+ // substituição
350
+ matrix[i][j - 1] + 1,
351
+ // inserção
352
+ matrix[i - 1][j] + 1
353
+ // deleção
354
+ );
355
+ }
356
+ }
357
+ }
358
+ return matrix[b.length][a.length];
359
+ }
360
+ function findSimilar(target, candidates, options = {}) {
361
+ const {
362
+ maxDistance = 3,
363
+ limit = 5,
364
+ normalize: normalize2 = true,
365
+ extractKey = (item) => String(item)
366
+ } = options;
367
+ const normalizedTarget = normalize2 ? target.toLowerCase() : target;
368
+ const scored = candidates.map((item) => {
369
+ const key = extractKey(item);
370
+ const normalizedKey = normalize2 ? key.toLowerCase() : key;
371
+ const keyNoExt = normalizedKey.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
372
+ let score = Infinity;
373
+ if (normalizedKey.includes(normalizedTarget) || keyNoExt.includes(normalizedTarget)) {
374
+ score = 0;
375
+ } else {
376
+ score = levenshteinDistance(keyNoExt, normalizedTarget);
377
+ }
378
+ return { item, score };
379
+ }).filter(({ score }) => score <= maxDistance).sort((a, b) => a.score - b.score).slice(0, limit);
380
+ return scored.map(({ item }) => item);
381
+ }
382
+ function findBestMatch(target, candidates, extractKey = (item) => String(item)) {
383
+ const similar = findSimilar(target, candidates, {
384
+ maxDistance: 2,
385
+ // Mais restritivo para "você quis dizer"
386
+ limit: 1,
387
+ extractKey
388
+ });
389
+ return similar.length > 0 ? similar[0] : null;
390
+ }
391
+ function extractFileName(filePath) {
392
+ const fileName = filePath.split("/").pop() || filePath;
393
+ return fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
394
+ }
395
+
146
396
  // src/formatters/text.ts
147
- function formatMapSummary(result, areasInfo) {
397
+ function formatMapSummary(result, areasInfo, ctx = "cli") {
148
398
  let out = "";
399
+ if (result.framework) {
400
+ out += `\u{1F3D7}\uFE0F Framework: ${result.framework}
401
+ `;
402
+ }
149
403
  out += `\u{1F4CA} ${result.summary.totalFiles} arquivos | ${result.summary.totalFolders} pastas
150
404
  `;
151
405
  const catOrder = [
@@ -172,9 +426,27 @@ function formatMapSummary(result, areasInfo) {
172
426
  }
173
427
  out += ` ${catParts.join(", ")}
174
428
  `;
429
+ const SMALL_PROJECT_THRESHOLD = 25;
430
+ if (result.summary.totalFiles <= SMALL_PROJECT_THRESHOLD && result.files.length > 0) {
431
+ out += `
432
+ \u{1F4C1} Arquivos:
433
+ `;
434
+ for (const file of result.files) {
435
+ const icon = categoryIcons[file.category];
436
+ out += ` ${icon} ${file.path}
437
+ `;
438
+ }
439
+ } else {
440
+ const topFolders = result.folders.sort((a, b) => b.fileCount - a.fileCount).slice(0, 5);
441
+ if (topFolders.length > 0) {
442
+ const folderParts = topFolders.map((f) => `${f.path}/ (${f.fileCount})`);
443
+ out += `\u{1F4C1} ${folderParts.join(", ")}
444
+ `;
445
+ }
446
+ }
175
447
  if (areasInfo && areasInfo.total > 0) {
176
448
  out += `
177
- \u{1F5C2}\uFE0F \xC1reas: ${areasInfo.names.join(", ")}
449
+ \u{1F5C2}\uFE0F Areas: ${areasInfo.names.join(", ")}
178
450
  `;
179
451
  }
180
452
  out += `
@@ -185,43 +457,30 @@ function formatMapSummary(result, areasInfo) {
185
457
  `;
186
458
  out += ` ${cloudFunctionCount} function(s) em functions/src/
187
459
  `;
188
- out += ` \u2192 Use 'ai-tool functions' para listar triggers
460
+ out += ` \u2192 ${hint("functions", ctx)}
189
461
 
190
462
  `;
191
463
  }
192
464
  if (result.circularDependencies.length > 0) {
193
- out += `\u26A0\uFE0F ${result.circularDependencies.length} depend\xEAncia(s) circular(es) detectada(s)
465
+ out += `\u26A0\uFE0F ${result.circularDependencies.length} dependencia(s) circular(es) detectada(s)
194
466
  `;
195
- out += ` \u2192 Use impact <arquivo> para investigar
467
+ out += ` \u2192 ${hint("impact", ctx)} para investigar
196
468
 
197
469
  `;
198
470
  }
199
471
  if (areasInfo && areasInfo.unmappedCount > 0) {
200
- out += `\u26A0\uFE0F ${areasInfo.unmappedCount} arquivo(s) sem \xE1rea definida
472
+ out += `\u26A0\uFE0F ${areasInfo.unmappedCount} arquivo(s) sem area definida
201
473
  `;
202
- out += ` \u2192 Use areas init para configurar
474
+ out += ` \u2192 ${hint("areas_init", ctx)} para configurar
203
475
 
204
476
  `;
205
477
  }
206
- out += `\u{1F4D6} Pr\xF3ximos passos:
207
- `;
208
- out += ` \u2192 area <nome> - ver arquivos de uma \xE1rea
209
- `;
210
- out += ` \u2192 suggest <arquivo> - o que ler antes de editar
211
- `;
212
- out += ` \u2192 context <arquivo> - ver API de um arquivo
213
- `;
478
+ out += nextSteps("map", ctx);
214
479
  return out;
215
480
  }
216
- function formatMapText(result) {
481
+ function formatMapText(result, ctx = "cli") {
217
482
  let out = "";
218
- out += `
219
- `;
220
- out += `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
221
- `;
222
- out += `\u2551 \u{1F4C1} PROJECT MAP \u2551
223
- `;
224
- out += `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
483
+ out += `## \u{1F4C1} PROJECT MAP
225
484
 
226
485
  `;
227
486
  out += `\u{1F4CA} RESUMO
@@ -230,7 +489,7 @@ function formatMapText(result) {
230
489
  `;
231
490
  out += ` Pastas: ${result.summary.totalFolders}
232
491
  `;
233
- out += ` Diret\xF3rio: ${result.cwd}
492
+ out += ` Diretorio: ${result.cwd}
234
493
 
235
494
  `;
236
495
  out += `\u{1F4C2} CATEGORIAS
@@ -272,7 +531,7 @@ function formatMapText(result) {
272
531
  }
273
532
  if (result.circularDependencies.length > 0) {
274
533
  out += `
275
- \u26A0\uFE0F DEPEND\xCANCIAS CIRCULARES (${result.circularDependencies.length})
534
+ \u26A0\uFE0F DEPENDENCIAS CIRCULARES (${result.circularDependencies.length})
276
535
  `;
277
536
  for (const cycle of result.circularDependencies.slice(0, 5)) {
278
537
  out += ` ${cycle.join(" \u2192 ")}
@@ -283,41 +542,37 @@ function formatMapText(result) {
283
542
  `;
284
543
  }
285
544
  }
545
+ out += nextSteps("map", ctx);
286
546
  return out;
287
547
  }
288
- function formatDeadText(result) {
548
+ function formatDeadText(result, ctx = "cli") {
289
549
  let out = "";
290
- out += `
291
- `;
292
- out += `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
293
- `;
294
- out += `\u2551 \u{1F480} DEAD CODE \u2551
295
- `;
296
- out += `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
550
+ out += `## \u{1F480} DEAD CODE
297
551
 
298
552
  `;
299
553
  if (result.summary.totalDead === 0) {
300
- out += `\u2705 Nenhum c\xF3digo morto encontrado!
554
+ out += `\u2705 Nenhum codigo morto encontrado!
301
555
  `;
302
- out += ` Todos os arquivos e exports est\xE3o sendo utilizados.
556
+ out += ` Todos os arquivos e exports estao sendo utilizados.
303
557
  `;
558
+ out += nextSteps("dead", ctx);
304
559
  return out;
305
560
  }
306
561
  out += `\u{1F4CA} RESUMO
307
562
  `;
308
- out += ` Total: ${result.summary.totalDead} itens n\xE3o utilizados
563
+ out += ` Total: ${result.summary.totalDead} itens nao utilizados
309
564
  `;
310
- out += ` Arquivos \xF3rf\xE3os: ${result.summary.byType.files}
565
+ out += ` Arquivos orfaos: ${result.summary.byType.files}
311
566
  `;
312
- out += ` Exports n\xE3o usados: ${result.summary.byType.exports}
567
+ out += ` Exports nao usados: ${result.summary.byType.exports}
313
568
  `;
314
- out += ` Depend\xEAncias n\xE3o usadas: ${result.summary.byType.dependencies}
569
+ out += ` Dependencias nao usadas: ${result.summary.byType.dependencies}
315
570
 
316
571
  `;
317
572
  if (result.files.length > 0) {
318
- out += `\u{1F5D1}\uFE0F ARQUIVOS \xD3RF\xC3OS (${result.files.length})
573
+ out += `\u{1F5D1}\uFE0F ARQUIVOS ORFAOS (${result.files.length})
319
574
  `;
320
- out += ` Arquivos que ningu\xE9m importa:
575
+ out += ` Arquivos que ninguem importa:
321
576
 
322
577
  `;
323
578
  const byCategory = /* @__PURE__ */ new Map();
@@ -344,7 +599,7 @@ function formatDeadText(result) {
344
599
  }
345
600
  }
346
601
  if (result.exports.length > 0) {
347
- out += `\u{1F4E4} EXPORTS N\xC3O USADOS (${result.exports.length})
602
+ out += `\u{1F4E4} EXPORTS NAO USADOS (${result.exports.length})
348
603
  `;
349
604
  for (const exp of result.exports.slice(0, 10)) {
350
605
  out += ` ${exp.file}: ${exp.export}
@@ -358,7 +613,7 @@ function formatDeadText(result) {
358
613
  `;
359
614
  }
360
615
  if (result.dependencies.length > 0) {
361
- out += `\u{1F4E6} DEPEND\xCANCIAS N\xC3O USADAS (${result.dependencies.length})
616
+ out += `\u{1F4E6} DEPENDENCIAS NAO USADAS (${result.dependencies.length})
362
617
  `;
363
618
  for (const dep of result.dependencies) {
364
619
  out += ` ${dep}
@@ -394,12 +649,12 @@ function formatDeadText(result) {
394
649
  `;
395
650
  out += ` 3. Ver detalhes em JSON:
396
651
  `;
397
- out += ` ai-tool dead --format=json
652
+ out += ` ${hint("dead", ctx)} --format=json
398
653
  `;
399
654
  const suggestions = generateIgnoreSuggestions(result);
400
655
  if (suggestions.length > 0) {
401
656
  out += `
402
- \u{1F3AF} SUGEST\xD5ES INTELIGENTES
657
+ \u{1F3AF} SUGESTOES INTELIGENTES
403
658
 
404
659
  `;
405
660
  for (const suggestion of suggestions) {
@@ -412,6 +667,7 @@ function formatDeadText(result) {
412
667
  `;
413
668
  }
414
669
  }
670
+ out += nextSteps("dead", ctx);
415
671
  return out;
416
672
  }
417
673
  function generateIgnoreSuggestions(result) {
@@ -431,7 +687,7 @@ function generateIgnoreSuggestions(result) {
431
687
  suggestions.push({
432
688
  icon: "\u{1F9EA}",
433
689
  pattern: "**/*.(test|spec).(ts|tsx|js|jsx)",
434
- reason: "Arquivos de teste geralmente s\xE3o entry points pr\xF3prios",
690
+ reason: "Arquivos de teste geralmente sao entry points proprios",
435
691
  count: testFiles.length
436
692
  });
437
693
  }
@@ -442,7 +698,7 @@ function generateIgnoreSuggestions(result) {
442
698
  suggestions.push({
443
699
  icon: "\u2699\uFE0F",
444
700
  pattern: "**/*.config.(ts|js|mjs|cjs)",
445
- reason: "Arquivos de configura\xE7\xE3o s\xE3o entry points",
701
+ reason: "Arquivos de configuracao sao entry points",
446
702
  count: configFiles.length
447
703
  });
448
704
  }
@@ -451,7 +707,7 @@ function generateIgnoreSuggestions(result) {
451
707
  suggestions.push({
452
708
  icon: "\u{1F4D8}",
453
709
  pattern: "**/*.d.ts",
454
- reason: "Arquivos de defini\xE7\xE3o TypeScript",
710
+ reason: "Arquivos de definicao TypeScript",
455
711
  count: dtsFiles.length
456
712
  });
457
713
  }
@@ -460,21 +716,15 @@ function generateIgnoreSuggestions(result) {
460
716
  suggestions.push({
461
717
  icon: "\u{1F4DC}",
462
718
  pattern: "scripts/**",
463
- reason: "Scripts de automa\xE7\xE3o s\xE3o entry points",
719
+ reason: "Scripts de automacao sao entry points",
464
720
  count: scriptFiles.length
465
721
  });
466
722
  }
467
723
  return suggestions;
468
724
  }
469
- function formatImpactText(result) {
725
+ function formatImpactText(result, ctx = "cli") {
470
726
  let out = "";
471
- out += `
472
- `;
473
- out += `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
474
- `;
475
- out += `\u2551 \u{1F3AF} IMPACT ANALYSIS \u2551
476
- `;
477
- out += `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
727
+ out += `## \u{1F3AF} IMPACT ANALYSIS
478
728
 
479
729
  `;
480
730
  const icon = categoryIcons[result.category];
@@ -483,10 +733,7 @@ function formatImpactText(result) {
483
733
  out += ` ${icon} ${result.category}
484
734
 
485
735
  `;
486
- out += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
487
-
488
- `;
489
- out += `\u2B06\uFE0F USADO POR (${result.upstream.total} arquivo${result.upstream.total !== 1 ? "s" : ""} \xFAnico${result.upstream.total !== 1 ? "s" : ""})
736
+ out += `\u2B06\uFE0F USADO POR (${result.upstream.total} arquivo${result.upstream.total !== 1 ? "s" : ""} unico${result.upstream.total !== 1 ? "s" : ""})
490
737
  `;
491
738
  if (result.upstream.direct.length > 0 || result.upstream.indirect.length > 0) {
492
739
  out += ` \u{1F4CD} ${result.upstream.direct.length} direto${result.upstream.direct.length !== 1 ? "s" : ""} + ${result.upstream.indirect.length} indireto${result.upstream.indirect.length !== 1 ? "s" : ""}
@@ -496,7 +743,7 @@ function formatImpactText(result) {
496
743
 
497
744
  `;
498
745
  if (result.upstream.total === 0) {
499
- out += ` Ningu\xE9m importa este arquivo diretamente.
746
+ out += ` Ninguem importa este arquivo diretamente.
500
747
  `;
501
748
  } else {
502
749
  for (const file of result.upstream.direct.slice(0, 10)) {
@@ -519,10 +766,8 @@ function formatImpactText(result) {
519
766
  }
520
767
  }
521
768
  out += `
522
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
523
-
524
769
  `;
525
- out += `\u2B07\uFE0F DEPEND\xCANCIAS (${result.downstream.total} arquivo${result.downstream.total !== 1 ? "s" : ""} \xFAnico${result.downstream.total !== 1 ? "s" : ""})
770
+ out += `\u2B07\uFE0F DEPENDENCIAS (${result.downstream.total} arquivo${result.downstream.total !== 1 ? "s" : ""} unico${result.downstream.total !== 1 ? "s" : ""})
526
771
  `;
527
772
  if (result.downstream.direct.length > 0 || result.downstream.indirect.length > 0) {
528
773
  out += ` \u{1F4CD} ${result.downstream.direct.length} direto${result.downstream.direct.length !== 1 ? "s" : ""} + ${result.downstream.indirect.length} indireto${result.downstream.indirect.length !== 1 ? "s" : ""}
@@ -532,7 +777,7 @@ function formatImpactText(result) {
532
777
 
533
778
  `;
534
779
  if (result.downstream.total === 0) {
535
- out += ` Este arquivo n\xE3o importa nenhum arquivo local.
780
+ out += ` Este arquivo nao importa nenhum arquivo local.
536
781
  `;
537
782
  } else {
538
783
  for (const file of result.downstream.direct.slice(0, 10)) {
@@ -546,15 +791,13 @@ function formatImpactText(result) {
546
791
  }
547
792
  }
548
793
  out += `
549
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
550
-
551
794
  `;
552
- out += `\u{1F4CA} M\xC9TRICAS DE IMPACTO
795
+ out += `\u{1F4CA} METRICAS DE IMPACTO
553
796
 
554
797
  `;
555
- out += ` Arquivos que importam este (upstream): ${result.upstream.total} \xFAnico${result.upstream.total !== 1 ? "s" : ""}
798
+ out += ` Arquivos que importam este (upstream): ${result.upstream.total} unico${result.upstream.total !== 1 ? "s" : ""}
556
799
  `;
557
- out += ` Arquivos que este importa (downstream): ${result.downstream.total} \xFAnico${result.downstream.total !== 1 ? "s" : ""}
800
+ out += ` Arquivos que este importa (downstream): ${result.downstream.total} unico${result.downstream.total !== 1 ? "s" : ""}
558
801
  `;
559
802
  if (result.risks.length > 0) {
560
803
  out += `
@@ -569,7 +812,7 @@ function formatImpactText(result) {
569
812
  }
570
813
  if (result.suggestions.length > 0) {
571
814
  out += `
572
- \u{1F4A1} SUGEST\xD5ES
815
+ \u{1F4A1} SUGESTOES
573
816
 
574
817
  `;
575
818
  for (const suggestion of result.suggestions) {
@@ -579,11 +822,11 @@ function formatImpactText(result) {
579
822
  }
580
823
  if (result.gitHistory && result.gitHistory.hasGitRepo) {
581
824
  out += `
582
- \u{1F4DC} HIST\xD3RICO GIT (\xFAltimos ${result.gitHistory.recentCommits.length} commits)
825
+ \u{1F4DC} HISTORICO GIT (ultimos ${result.gitHistory.recentCommits.length} commits)
583
826
 
584
827
  `;
585
828
  if (result.gitHistory.recentCommits.length === 0) {
586
- out += ` Arquivo n\xE3o est\xE1 no reposit\xF3rio Git ou sem hist\xF3rico.
829
+ out += ` Arquivo nao esta no repositorio Git ou sem historico.
587
830
  `;
588
831
  } else {
589
832
  for (const commit of result.gitHistory.recentCommits) {
@@ -599,17 +842,12 @@ function formatImpactText(result) {
599
842
  }
600
843
  }
601
844
  }
845
+ out += nextSteps("impact", ctx);
602
846
  return out;
603
847
  }
604
- function formatSuggestText(result) {
848
+ function formatSuggestText(result, ctx = "cli") {
605
849
  let out = "";
606
- out += `
607
- `;
608
- out += `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
609
- `;
610
- out += `\u2551 \u{1F4DA} SUGGEST \u2551
611
- `;
612
- out += `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
850
+ out += `## \u{1F4DA} SUGGEST
613
851
 
614
852
  `;
615
853
  const icon = categoryIcons[result.category];
@@ -623,11 +861,9 @@ function formatSuggestText(result) {
623
861
  `;
624
862
  out += ` Este arquivo nao tem dependencias ou arquivos relacionados.
625
863
  `;
864
+ out += nextSteps("suggest", ctx);
626
865
  return out;
627
866
  }
628
- out += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
629
-
630
- `;
631
867
  const byPriority = {
632
868
  critical: result.suggestions.filter((s) => s.priority === "critical"),
633
869
  high: result.suggestions.filter((s) => s.priority === "high"),
@@ -698,9 +934,6 @@ function formatSuggestText(result) {
698
934
  out += `
699
935
  `;
700
936
  }
701
- out += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
702
-
703
- `;
704
937
  out += `\u{1F4CA} RESUMO
705
938
  `;
706
939
  out += ` Total de arquivos sugeridos: ${result.suggestions.length}
@@ -723,10 +956,7 @@ function formatSuggestText(result) {
723
956
  }
724
957
  if (result.testSuggestions.length > 0) {
725
958
  out += `
726
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
727
-
728
- `;
729
- out += `\u{1F9EA} TESTES E VERIFICA\xC7\xD5ES
959
+ \u{1F9EA} TESTES E VERIFICACOES
730
960
 
731
961
  `;
732
962
  for (const testSuggestion of result.testSuggestions) {
@@ -734,17 +964,12 @@ function formatSuggestText(result) {
734
964
  `;
735
965
  }
736
966
  }
967
+ out += nextSteps("suggest", ctx);
737
968
  return out;
738
969
  }
739
- function formatContextText(result) {
970
+ function formatContextText(result, ctx = "cli") {
740
971
  let out = "";
741
- out += `
742
- `;
743
- out += `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
744
- `;
745
- out += `\u2551 \u{1F4C4} CONTEXT \u2551
746
- `;
747
- out += `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
972
+ out += `## \u{1F4C4} CONTEXT
748
973
 
749
974
  `;
750
975
  const icon = categoryIcons[result.category];
@@ -752,9 +977,6 @@ function formatContextText(result) {
752
977
  `;
753
978
  out += ` ${icon} ${result.category}
754
979
 
755
- `;
756
- out += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
757
-
758
980
  `;
759
981
  if (result.imports.length > 0) {
760
982
  out += `\u{1F4E5} IMPORTS (${result.imports.length})
@@ -798,10 +1020,10 @@ function formatContextText(result) {
798
1020
  `;
799
1021
  for (const fn of result.functions) {
800
1022
  const exported = fn.isExported ? "export " : "";
801
- const async = fn.isAsync ? "async " : "";
1023
+ const asyncLabel = fn.isAsync ? "async " : "";
802
1024
  const arrow = fn.isArrowFunction ? " =>" : "";
803
1025
  const params = fn.params.map((p) => `${p.name}: ${p.type}`).join(", ");
804
- out += ` ${exported}${async}${fn.name}(${params})${arrow}: ${fn.returnType}
1026
+ out += ` ${exported}${asyncLabel}${fn.name}(${params})${arrow}: ${fn.returnType}
805
1027
  `;
806
1028
  if (fn.jsdoc) {
807
1029
  out += ` /** ${fn.jsdoc} */
@@ -811,9 +1033,18 @@ function formatContextText(result) {
811
1033
  `;
812
1034
  }
813
1035
  }
814
- out += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1036
+ if (result.constants && result.constants.length > 0) {
1037
+ out += `\u{1F4CC} CONSTANTS (${result.constants.length})
815
1038
 
816
1039
  `;
1040
+ for (const c of result.constants) {
1041
+ const exported = c.isExported ? "export " : "";
1042
+ out += ` ${exported}${c.name}: ${c.type}
1043
+ `;
1044
+ }
1045
+ out += `
1046
+ `;
1047
+ }
817
1048
  out += `\u{1F4CA} RESUMO
818
1049
  `;
819
1050
  out += ` Imports: ${result.imports.length}
@@ -824,35 +1055,60 @@ function formatContextText(result) {
824
1055
  `;
825
1056
  out += ` Functions: ${result.functions.length}
826
1057
  `;
827
- return out;
828
- }
829
- function formatAreasText(result) {
830
- let out = "";
1058
+ if (result.constants && result.constants.length > 0) {
1059
+ out += ` Constants: ${result.constants.length}
1060
+ `;
1061
+ }
831
1062
  out += `
1063
+ \u{1F4D6} Proximos passos:
832
1064
  `;
833
- out += `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
1065
+ const exportedFns = result.functions.filter((f) => f.isExported);
1066
+ const exportedTypes = result.types.filter((t) => t.isExported);
1067
+ if (exportedFns.length > 0) {
1068
+ const mainExport = exportedFns[0].name;
1069
+ out += ` \u2192 ${hint("find", ctx, { "<termo>": mainExport })} - ver onde ${mainExport} e usado
1070
+ `;
1071
+ }
1072
+ const isHook = result.category === "hook" || result.functions.some((f) => f.name.startsWith("use"));
1073
+ if (isHook) {
1074
+ out += ` \u2192 ${hint("impact", ctx, { "<arquivo>": result.file })} - ver quem usa este hook
834
1075
  `;
835
- out += `\u2551 \u{1F4E6} PROJECT AREAS \u2551
1076
+ }
1077
+ if (result.category === "service") {
1078
+ out += ` \u2192 ${hint("impact", ctx, { "<arquivo>": result.file })} - ver quem usa este service
836
1079
  `;
837
- out += `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
1080
+ }
1081
+ if (exportedTypes.length > 0) {
1082
+ const mainType = exportedTypes[0].name;
1083
+ out += ` \u2192 ${hint("find", ctx, { "<termo>": mainType })} - ver onde ${mainType} e usado
1084
+ `;
1085
+ }
1086
+ if (!isHook && result.category !== "service") {
1087
+ out += ` \u2192 ${hint("impact", ctx, { "<arquivo>": result.file })} - ver quem sera afetado por mudancas
1088
+ `;
1089
+ }
1090
+ out += ` \u2192 ${hint("suggest", ctx, { "<arquivo>": result.file })} - o que ler antes de editar
1091
+ `;
1092
+ return out;
1093
+ }
1094
+ function formatAreasText(result, ctx = "cli") {
1095
+ let out = "";
1096
+ out += `## \u{1F4E6} PROJECT AREAS
838
1097
 
839
1098
  `;
840
1099
  out += `\u{1F4CA} RESUMO
841
1100
  `;
842
- out += ` \xC1reas: ${result.summary.totalAreas}
1101
+ out += ` Areas: ${result.summary.totalAreas}
843
1102
  `;
844
1103
  out += ` Arquivos: ${result.summary.totalFiles}
845
1104
  `;
846
1105
  if (result.summary.unmappedCount > 0) {
847
- out += ` \u26A0\uFE0F Sem \xE1rea: ${result.summary.unmappedCount}
1106
+ out += ` \u26A0\uFE0F Sem area: ${result.summary.unmappedCount}
848
1107
  `;
849
1108
  }
850
1109
  out += `
851
1110
  `;
852
- out += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
853
-
854
- `;
855
- out += `\u{1F4E6} \xC1REAS DETECTADAS
1111
+ out += `\u{1F4E6} AREAS DETECTADAS
856
1112
 
857
1113
  `;
858
1114
  for (const area2 of result.areas) {
@@ -872,15 +1128,12 @@ function formatAreasText(result) {
872
1128
  `;
873
1129
  }
874
1130
  if (result.unmapped.length > 0) {
875
- out += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
876
-
877
- `;
878
- out += `\u26A0\uFE0F ARQUIVOS SEM \xC1REA (${result.unmapped.length})
1131
+ out += `\u26A0\uFE0F ARQUIVOS SEM AREA (${result.unmapped.length})
879
1132
 
880
1133
  `;
881
1134
  for (const file of result.unmapped.slice(0, 10)) {
882
- const icon = categoryIcons[file.category];
883
- out += ` ${icon} ${file.path}
1135
+ const fileIcon = categoryIcons[file.category];
1136
+ out += ` ${fileIcon} ${file.path}
884
1137
  `;
885
1138
  }
886
1139
  if (result.unmapped.length > 10) {
@@ -888,32 +1141,19 @@ function formatAreasText(result) {
888
1141
  `;
889
1142
  }
890
1143
  out += `
891
- \u{1F4A1} Adicione padr\xF5es em .analyze/areas.config.json
1144
+ \u{1F4A1} Adicione padroes em .analyze/areas.config.json
892
1145
  `;
893
- out += ` ou execute 'ai-tool areas init' para gerar configura\xE7\xE3o
1146
+ out += ` ou execute ${hint("areas_init", ctx)} para gerar configuracao
894
1147
  `;
895
1148
  }
896
- out += `
897
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
898
-
899
- `;
900
- out += `\u{1F4A1} Use 'ai-tool area <nome>' para ver detalhes de uma \xE1rea
901
- `;
902
- out += ` Exemplo: ai-tool area auth
903
- `;
1149
+ out += nextSteps("areas", ctx);
904
1150
  return out;
905
1151
  }
906
- function formatAreaDetailText(result, options = {}) {
1152
+ function formatAreaDetailText(result, options = {}, ctx = "cli") {
907
1153
  const { full = false, filterType } = options;
908
1154
  const { area: area2, byCategory } = result;
909
1155
  let out = "";
910
- out += `
911
- `;
912
- out += `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
913
- `;
914
- out += `\u2551 \u{1F4E6} AREA DETAIL \u2551
915
- `;
916
- out += `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
1156
+ out += `## \u{1F4E6} AREA DETAIL
917
1157
 
918
1158
  `;
919
1159
  out += `\u{1F4E6} ${area2.name}
@@ -951,9 +1191,6 @@ function formatAreaDetailText(result, options = {}) {
951
1191
  out += catParts.join(" ");
952
1192
  out += `
953
1193
 
954
- `;
955
- out += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
956
-
957
1194
  `;
958
1195
  if (filterType) {
959
1196
  const files = byCategory[filterType] || [];
@@ -965,6 +1202,7 @@ function formatAreaDetailText(result, options = {}) {
965
1202
  out += ` ${file.path}${desc}
966
1203
  `;
967
1204
  }
1205
+ out += nextSteps("area", ctx);
968
1206
  return out;
969
1207
  }
970
1208
  const maxFilesPerCategory = full ? 100 : 8;
@@ -996,17 +1234,12 @@ function formatAreaDetailText(result, options = {}) {
996
1234
  out += `\u{1F4A1} Use --full para ver todos os arquivos
997
1235
  `;
998
1236
  }
1237
+ out += nextSteps("area", ctx);
999
1238
  return out;
1000
1239
  }
1001
- function formatFindText(result) {
1240
+ function formatFindText(result, ctx = "cli", symbolNames) {
1002
1241
  let out = "";
1003
- out += `
1004
- `;
1005
- out += `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
1006
- `;
1007
- out += `\u2551 \u{1F50D} FIND \u2551
1008
- `;
1009
- out += `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
1242
+ out += `## \u{1F50D} FIND
1010
1243
 
1011
1244
  `;
1012
1245
  if (result.query) {
@@ -1021,7 +1254,7 @@ function formatFindText(result) {
1021
1254
 
1022
1255
  `;
1023
1256
  } else {
1024
- out += `\u{1F4CB} Listando todos os s\xEDmbolos do tipo: ${result.filters.type || "all"}
1257
+ out += `\u{1F4CB} Listando todos os simbolos do tipo: ${result.filters.type || "all"}
1025
1258
 
1026
1259
  `;
1027
1260
  }
@@ -1031,33 +1264,45 @@ function formatFindText(result) {
1031
1264
 
1032
1265
  `;
1033
1266
  } else {
1034
- out += `\u274C Nenhum s\xEDmbolo do tipo "${result.filters.type}" encontrado
1267
+ out += `\u274C Nenhum simbolo do tipo "${result.filters.type}" encontrado
1035
1268
 
1036
1269
  `;
1037
1270
  }
1271
+ if (symbolNames && symbolNames.length > 0 && result.query) {
1272
+ const similar = findSimilar(result.query, symbolNames, { maxDistance: 3, limit: 5 });
1273
+ if (similar.length > 0) {
1274
+ out += `\u{1F4A1} Voce quis dizer?
1275
+ `;
1276
+ for (const name of similar) {
1277
+ out += ` \u2192 ${hint("find", ctx, { "<termo>": name })}
1278
+ `;
1279
+ }
1280
+ out += `
1281
+ `;
1282
+ }
1283
+ }
1038
1284
  out += `\u{1F4A1} Dicas:
1039
1285
  `;
1040
- out += ` \u2022 Verifique a ortografia
1286
+ out += ` \u2192 Verifique a ortografia
1287
+ `;
1288
+ out += ` \u2192 Tente buscar parte do nome
1041
1289
  `;
1042
- out += ` \u2022 Tente buscar parte do nome
1290
+ out += ` \u2192 Remova filtros de tipo ou area
1043
1291
  `;
1044
- out += ` \u2022 Remova filtros de tipo ou \xE1rea
1292
+ out += ` \u2192 ${hint("describe", ctx)} - buscar areas por descricao
1045
1293
  `;
1046
1294
  return out;
1047
1295
  }
1048
- out += `\u{1F4CA} ${result.summary.definitions} defini\xE7\xE3o, ${result.summary.references} refer\xEAncias em ${result.summary.files} arquivos
1049
-
1050
- `;
1051
- out += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1296
+ out += `\u{1F4CA} ${result.summary.definitions} definicao, ${result.summary.references} referencias em ${result.summary.files} arquivos
1052
1297
 
1053
1298
  `;
1054
1299
  if (result.definition) {
1055
- out += `\u{1F4CD} DEFINI\xC7\xC3O
1300
+ out += `\u{1F4CD} DEFINICAO
1056
1301
 
1057
1302
  `;
1058
1303
  const def = result.definition;
1059
- const icon = categoryIcons[def.category];
1060
- out += ` ${icon} ${def.file}:${def.line}
1304
+ const defIcon = categoryIcons[def.category];
1305
+ out += ` ${defIcon} ${def.file}:${def.line}
1061
1306
  `;
1062
1307
  out += ` ${def.code}
1063
1308
  `;
@@ -1072,8 +1317,8 @@ function formatFindText(result) {
1072
1317
 
1073
1318
  `;
1074
1319
  for (const ref of imports.slice(0, 10)) {
1075
- const icon = categoryIcons[ref.category];
1076
- out += ` ${icon} ${ref.file}:${ref.line}
1320
+ const refIcon = categoryIcons[ref.category];
1321
+ out += ` ${refIcon} ${ref.file}:${ref.line}
1077
1322
  `;
1078
1323
  out += ` ${ref.code}
1079
1324
  `;
@@ -1090,8 +1335,8 @@ function formatFindText(result) {
1090
1335
 
1091
1336
  `;
1092
1337
  for (const ref of usages.slice(0, 15)) {
1093
- const icon = categoryIcons[ref.category];
1094
- out += ` ${icon} ${ref.file}:${ref.line}
1338
+ const refIcon = categoryIcons[ref.category];
1339
+ out += ` ${refIcon} ${ref.file}:${ref.line}
1095
1340
  `;
1096
1341
  out += ` ${ref.code}
1097
1342
  `;
@@ -1106,15 +1351,9 @@ function formatFindText(result) {
1106
1351
  }
1107
1352
  return out;
1108
1353
  }
1109
- function formatAreaContextText(result) {
1354
+ function formatAreaContextText(result, ctx = "cli") {
1110
1355
  let out = "";
1111
- out += `
1112
- `;
1113
- out += `\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
1114
- `;
1115
- out += `\u2551 \u{1F4E6} AREA CONTEXT \u2551
1116
- `;
1117
- out += `\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
1356
+ out += `## \u{1F4E6} AREA CONTEXT
1118
1357
 
1119
1358
  `;
1120
1359
  out += `\u{1F4E6} ${result.area.name} - Contexto Consolidado (${result.area.fileCount} arquivos)
@@ -1124,9 +1363,6 @@ function formatAreaContextText(result) {
1124
1363
  `;
1125
1364
  }
1126
1365
  out += `
1127
- `;
1128
- out += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1129
-
1130
1366
  `;
1131
1367
  if (result.types.length > 0) {
1132
1368
  out += `\u{1F3F7}\uFE0F TYPES (${result.types.length})
@@ -1232,9 +1468,6 @@ function formatAreaContextText(result) {
1232
1468
  `;
1233
1469
  }
1234
1470
  }
1235
- out += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1236
-
1237
- `;
1238
1471
  out += `\u{1F4CA} RESUMO
1239
1472
  `;
1240
1473
  out += ` Types: ${result.types.length}
@@ -1253,6 +1486,7 @@ function formatAreaContextText(result) {
1253
1486
  out += ` Triggers: ${result.triggers.length}
1254
1487
  `;
1255
1488
  }
1489
+ out += nextSteps("area_context", ctx);
1256
1490
  return out;
1257
1491
  }
1258
1492
 
@@ -1829,10 +2063,37 @@ function formatOutput(result, format, textFormatter, cacheMarker) {
1829
2063
  }
1830
2064
 
1831
2065
  // src/commands/map.ts
2066
+ import { existsSync as existsSync4 } from "fs";
2067
+ import { join as join4 } from "path";
2068
+ function detectFramework(cwd) {
2069
+ if (existsSync4(join4(cwd, "next.config.js")) || existsSync4(join4(cwd, "next.config.ts")) || existsSync4(join4(cwd, "next.config.mjs"))) {
2070
+ return "Next.js";
2071
+ }
2072
+ if (existsSync4(join4(cwd, "vite.config.ts")) || existsSync4(join4(cwd, "vite.config.js"))) {
2073
+ return "Vite";
2074
+ }
2075
+ if (existsSync4(join4(cwd, "remix.config.js")) || existsSync4(join4(cwd, "remix.config.ts"))) {
2076
+ return "Remix";
2077
+ }
2078
+ if (existsSync4(join4(cwd, "astro.config.mjs")) || existsSync4(join4(cwd, "astro.config.ts"))) {
2079
+ return "Astro";
2080
+ }
2081
+ if (existsSync4(join4(cwd, "nuxt.config.ts")) || existsSync4(join4(cwd, "nuxt.config.js"))) {
2082
+ return "Nuxt";
2083
+ }
2084
+ if (existsSync4(join4(cwd, "svelte.config.js")) || existsSync4(join4(cwd, "svelte.config.ts"))) {
2085
+ return "SvelteKit";
2086
+ }
2087
+ if (existsSync4(join4(cwd, ".firebaserc")) || existsSync4(join4(cwd, "firebase.json"))) {
2088
+ return "Firebase";
2089
+ }
2090
+ return void 0;
2091
+ }
1832
2092
  async function map(options = {}) {
1833
2093
  const { cwd, format } = parseCommandOptions(options);
1834
2094
  const useCache = options.cache !== false;
1835
2095
  const full = options.full ?? false;
2096
+ const ctx = options.ctx || "cli";
1836
2097
  if (useCache && isCacheValid(cwd)) {
1837
2098
  const cached = getCachedMapResult(cwd);
1838
2099
  if (cached) {
@@ -1841,10 +2102,10 @@ async function map(options = {}) {
1841
2102
  return JSON.stringify(result, null, 2);
1842
2103
  }
1843
2104
  if (full) {
1844
- return formatMapText(result) + "\n\n\u{1F4E6} (resultado do cache)";
2105
+ return formatMapText(result, ctx) + "\n\n\u{1F4E6} (resultado do cache)";
1845
2106
  }
1846
2107
  const areasInfo = detectAreasInfo(cwd, result.files.map((f) => f.path));
1847
- return formatMapSummary(result, areasInfo) + "\n\u{1F4E6} (cache)";
2108
+ return formatMapSummary(result, areasInfo, ctx) + "\n\u{1F4E6} (cache)";
1848
2109
  }
1849
2110
  }
1850
2111
  try {
@@ -1898,10 +2159,12 @@ async function map(options = {}) {
1898
2159
  categories[file.category] = (categories[file.category] || 0) + 1;
1899
2160
  }
1900
2161
  const circular = findCircularDependencies();
2162
+ const framework = detectFramework(cwd);
1901
2163
  const result = {
1902
2164
  version: "1.0.0",
1903
2165
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1904
2166
  cwd,
2167
+ framework,
1905
2168
  summary: {
1906
2169
  totalFiles: files.length,
1907
2170
  totalFolders: folderMap.size,
@@ -1919,10 +2182,10 @@ async function map(options = {}) {
1919
2182
  return JSON.stringify(result, null, 2);
1920
2183
  }
1921
2184
  if (full) {
1922
- return formatMapText(result);
2185
+ return formatMapText(result, ctx);
1923
2186
  }
1924
2187
  const areasInfo = detectAreasInfo(cwd, files.map((f) => f.path));
1925
- return formatMapSummary(result, areasInfo);
2188
+ return formatMapSummary(result, areasInfo, ctx);
1926
2189
  } catch (error) {
1927
2190
  const message = error instanceof Error ? error.message : String(error);
1928
2191
  throw new Error(`Erro ao executar map: ${message}`);
@@ -1960,8 +2223,8 @@ function detectAreasInfo(cwd, filePaths) {
1960
2223
 
1961
2224
  // src/commands/dead.ts
1962
2225
  import { execSync } from "child_process";
1963
- import { writeFileSync as writeFileSync3, unlinkSync, existsSync as existsSync4 } from "fs";
1964
- import { join as join4 } from "path";
2226
+ import { writeFileSync as writeFileSync3, unlinkSync, existsSync as existsSync5 } from "fs";
2227
+ import { join as join5 } from "path";
1965
2228
  function generateKnipConfig(cwd) {
1966
2229
  const ignorePatterns = getIgnorePatterns(cwd);
1967
2230
  if (ignorePatterns.length === 0) {
@@ -1971,7 +2234,7 @@ function generateKnipConfig(cwd) {
1971
2234
  $schema: "https://unpkg.com/knip@5/schema.json",
1972
2235
  ignore: ignorePatterns
1973
2236
  };
1974
- const configPath = join4(cwd, ".knip.ai-tool.json");
2237
+ const configPath = join5(cwd, ".knip.ai-tool.json");
1975
2238
  writeFileSync3(configPath, JSON.stringify(knipConfig, null, 2), "utf-8");
1976
2239
  return configPath;
1977
2240
  }
@@ -2009,7 +2272,7 @@ async function dead(options = {}) {
2009
2272
  knipOutput = {};
2010
2273
  }
2011
2274
  } finally {
2012
- if (knipConfigPath && existsSync4(knipConfigPath)) {
2275
+ if (knipConfigPath && existsSync5(knipConfigPath)) {
2013
2276
  try {
2014
2277
  unlinkSync(knipConfigPath);
2015
2278
  } catch {
@@ -2091,93 +2354,34 @@ ${output}`;
2091
2354
  // src/commands/impact.ts
2092
2355
  import skott2 from "skott";
2093
2356
 
2094
- // src/utils/similarity.ts
2095
- function levenshteinDistance(a, b) {
2096
- const matrix = [];
2097
- for (let i = 0; i <= b.length; i++) {
2098
- matrix[i] = [i];
2099
- }
2100
- for (let j = 0; j <= a.length; j++) {
2101
- matrix[0][j] = j;
2102
- }
2103
- for (let i = 1; i <= b.length; i++) {
2104
- for (let j = 1; j <= a.length; j++) {
2105
- if (b.charAt(i - 1) === a.charAt(j - 1)) {
2106
- matrix[i][j] = matrix[i - 1][j - 1];
2107
- } else {
2108
- matrix[i][j] = Math.min(
2109
- matrix[i - 1][j - 1] + 1,
2110
- // substituição
2111
- matrix[i][j - 1] + 1,
2112
- // inserção
2113
- matrix[i - 1][j] + 1
2114
- // deleção
2115
- );
2116
- }
2117
- }
2118
- }
2119
- return matrix[b.length][a.length];
2120
- }
2121
- function findSimilar(target, candidates, options = {}) {
2122
- const {
2123
- maxDistance = 3,
2124
- limit = 5,
2125
- normalize: normalize2 = true,
2126
- extractKey = (item) => String(item)
2127
- } = options;
2128
- const normalizedTarget = normalize2 ? target.toLowerCase() : target;
2129
- const scored = candidates.map((item) => {
2130
- const key = extractKey(item);
2131
- const normalizedKey = normalize2 ? key.toLowerCase() : key;
2132
- const keyNoExt = normalizedKey.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2133
- let score = Infinity;
2134
- if (normalizedKey.includes(normalizedTarget) || keyNoExt.includes(normalizedTarget)) {
2135
- score = 0;
2136
- } else {
2137
- score = levenshteinDistance(keyNoExt, normalizedTarget);
2138
- }
2139
- return { item, score };
2140
- }).filter(({ score }) => score <= maxDistance).sort((a, b) => a.score - b.score).slice(0, limit);
2141
- return scored.map(({ item }) => item);
2142
- }
2143
- function findBestMatch(target, candidates, extractKey = (item) => String(item)) {
2144
- const similar = findSimilar(target, candidates, {
2145
- maxDistance: 2,
2146
- // Mais restritivo para "você quis dizer"
2147
- limit: 1,
2148
- extractKey
2149
- });
2150
- return similar.length > 0 ? similar[0] : null;
2151
- }
2152
- function extractFileName(filePath) {
2153
- const fileName = filePath.split("/").pop() || filePath;
2154
- return fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2155
- }
2156
-
2157
2357
  // src/utils/errors.ts
2158
- var COMMAND_REFERENCE = {
2159
- map: "Resumo do projeto (sem target)",
2160
- areas: "Listar todas as \xE1reas (sem target)",
2161
- area: "Arquivos de uma \xE1rea espec\xEDfica",
2358
+ var COMMAND_REFERENCE_KEYS = ["map", "areas", "area", "suggest", "context", "impact", "dead", "find", "describe"];
2359
+ var COMMAND_DESCRIPTIONS = {
2360
+ map: "Resumo do projeto",
2361
+ areas: "Listar todas as areas",
2362
+ area: "Arquivos de uma area especifica",
2162
2363
  suggest: "O que ler antes de editar",
2163
2364
  context: "API/assinaturas de um arquivo",
2164
2365
  impact: "Quem usa este arquivo",
2165
- dead: "C\xF3digo morto (sem target)"
2366
+ dead: "Codigo morto",
2367
+ find: "Buscar simbolos no codigo",
2368
+ describe: "Buscar areas por descricao"
2166
2369
  };
2167
- function getCommandReferenceSection(excludeCommand) {
2370
+ function getCommandReferenceSection(ctx, excludeCommand) {
2168
2371
  let out = `
2169
- \u{1F4CC} Comandos \xFAteis:
2372
+ \u{1F4CC} Comandos uteis:
2170
2373
  `;
2171
- for (const [cmd, desc] of Object.entries(COMMAND_REFERENCE)) {
2374
+ for (const cmd of COMMAND_REFERENCE_KEYS) {
2172
2375
  if (cmd !== excludeCommand) {
2173
- out += ` ai-tool ${cmd.padEnd(10)} ${desc}
2376
+ const desc = COMMAND_DESCRIPTIONS[cmd];
2377
+ out += ` \u2192 ${hint(cmd, ctx)} - ${desc}
2174
2378
  `;
2175
2379
  }
2176
2380
  }
2177
2381
  return out;
2178
2382
  }
2179
2383
  function formatFileNotFound(options) {
2180
- const { target, allFiles, command } = options;
2384
+ const { target, allFiles, command, ctx = "cli" } = options;
2181
2385
  const similarFiles = findSimilar(target, allFiles, {
2182
2386
  maxDistance: 3,
2183
2387
  limit: 5,
@@ -2185,14 +2389,20 @@ function formatFileNotFound(options) {
2185
2389
  });
2186
2390
  const bestMatch = findBestMatch(target, allFiles, extractFileName);
2187
2391
  let out = `
2188
- \u274C Arquivo n\xE3o encontrado: "${target}"
2392
+ \u274C Arquivo nao encontrado: "${target}"
2189
2393
 
2190
2394
  `;
2191
2395
  out += `\u{1F4CA} Total de arquivos indexados: ${allFiles.length}
2192
2396
 
2193
2397
  `;
2194
- if (bestMatch) {
2195
- out += `\u{1F4A1} Voc\xEA quis dizer?
2398
+ if (bestMatch && command) {
2399
+ out += `\u{1F4A1} Voce quis dizer?
2400
+ `;
2401
+ out += ` \u2192 ${hint(command, ctx, { "<arquivo>": bestMatch, "<termo>": bestMatch })}
2402
+
2403
+ `;
2404
+ } else if (bestMatch) {
2405
+ out += `\u{1F4A1} Voce quis dizer?
2196
2406
  `;
2197
2407
  out += ` \u2192 ${bestMatch}
2198
2408
 
@@ -2215,15 +2425,15 @@ function formatFileNotFound(options) {
2215
2425
  `;
2216
2426
  out += ` \u2022 Ou apenas o nome do arquivo: Header
2217
2427
  `;
2218
- out += ` \u2022 Verifique se o arquivo est\xE1 em uma pasta inclu\xEDda no scan
2428
+ out += ` \u2022 Verifique se o arquivo esta em uma pasta incluida no scan
2219
2429
  `;
2220
2430
  if (command) {
2221
- out += getCommandReferenceSection(command);
2431
+ out += getCommandReferenceSection(ctx, command);
2222
2432
  }
2223
2433
  return out;
2224
2434
  }
2225
2435
  function formatAreaNotFound(options) {
2226
- const { target, availableAreas } = options;
2436
+ const { target, availableAreas, ctx = "cli" } = options;
2227
2437
  const areaIds = availableAreas.map((a) => a.id);
2228
2438
  const bestMatchId = findBestMatch(target, areaIds);
2229
2439
  const similarAreaIds = findSimilar(target, areaIds, {
@@ -2231,18 +2441,18 @@ function formatAreaNotFound(options) {
2231
2441
  limit: 5
2232
2442
  });
2233
2443
  let out = `
2234
- \u274C \xC1rea n\xE3o encontrada: "${target}"
2444
+ \u274C Area nao encontrada: "${target}"
2235
2445
 
2236
2446
  `;
2237
2447
  if (bestMatchId) {
2238
- out += `\u{1F4A1} Voc\xEA quis dizer?
2448
+ out += `\u{1F4A1} Voce quis dizer?
2239
2449
  `;
2240
- out += ` \u2192 ai-tool area ${bestMatchId}
2450
+ out += ` \u2192 ${hint("area", ctx, { "<nome>": bestMatchId })}
2241
2451
 
2242
2452
  `;
2243
2453
  }
2244
2454
  if (availableAreas.length > 0) {
2245
- out += `\u{1F4E6} \xC1reas dispon\xEDveis:
2455
+ out += `\u{1F4E6} Areas disponiveis:
2246
2456
 
2247
2457
  `;
2248
2458
  if (similarAreaIds.length > 0 && !bestMatchId) {
@@ -2271,67 +2481,57 @@ function formatAreaNotFound(options) {
2271
2481
  }
2272
2482
  out += `\u{1F4D6} Dicas:
2273
2483
  `;
2274
- out += ` \u2022 Use o ID exato da \xE1rea (ex: ai-tool area auth)
2275
- `;
2276
- out += ` \u2022 Use 'ai-tool areas' para listar todas as \xE1reas
2484
+ out += ` \u2192 ${hint("areas", ctx)} - listar todas as areas
2277
2485
  `;
2278
- out += ` \u2022 IDs s\xE3o case-sensitive (Auth \u2260 auth)
2486
+ out += ` \u2192 ${hint("describe", ctx)} - buscar areas por descricao
2279
2487
  `;
2280
- out += `
2281
- \u{1F4CC} Comandos relacionados:
2282
- `;
2283
- out += ` ai-tool areas Listar todas as \xE1reas
2284
- `;
2285
- out += ` ai-tool map Ver estrutura do projeto
2488
+ out += ` \u2192 IDs sao case-sensitive (Auth \u2260 auth)
2286
2489
  `;
2287
2490
  return out;
2288
2491
  }
2289
- function formatMissingTarget(command) {
2492
+ function formatMissingTarget(command, ctx = "cli") {
2290
2493
  let out = `
2291
- \u274C Erro: par\xE2metro "target" \xE9 OBRIGAT\xD3RIO para o comando "${command}".
2494
+ \u274C Erro: parametro "target" e OBRIGATORIO para o comando "${command}".
2292
2495
 
2293
2496
  `;
2294
2497
  out += `\u{1F4DD} Exemplos:
2295
2498
  `;
2296
2499
  if (command === "area") {
2297
- out += ` ai-tool area auth
2500
+ out += ` ${hint("area", ctx, { "<nome>": "auth" })}
2298
2501
  `;
2299
- out += ` ai-tool area dashboard
2300
- `;
2301
- out += ` ai-tool area billing --type=hook
2502
+ out += ` ${hint("area", ctx, { "<nome>": "dashboard" })}
2302
2503
 
2303
2504
  `;
2304
- out += `\u{1F4A1} Use 'ai-tool areas' para listar todas as \xE1reas dispon\xEDveis.
2505
+ out += `\u{1F4A1} ${hint("areas", ctx)} - listar todas as areas disponiveis
2305
2506
  `;
2306
2507
  } else {
2307
- out += ` ai-tool ${command} useAuth
2308
- `;
2309
- out += ` ai-tool ${command} Button.tsx
2508
+ out += ` ${hint(command, ctx, { "<arquivo>": "useAuth", "<termo>": "useAuth" })}
2310
2509
  `;
2311
- out += ` ai-tool ${command} src/hooks/useAuth.ts
2510
+ out += ` ${hint(command, ctx, { "<arquivo>": "Button.tsx", "<termo>": "Button" })}
2312
2511
  `;
2313
2512
  }
2314
- out += getCommandReferenceSection(command);
2513
+ out += getCommandReferenceSection(ctx, command);
2315
2514
  return out;
2316
2515
  }
2317
- function formatInvalidCommand(command) {
2318
- const validCommands = Object.keys(COMMAND_REFERENCE);
2516
+ function formatInvalidCommand(command, ctx = "cli") {
2517
+ const validCommands = Object.keys(COMMAND_DESCRIPTIONS);
2319
2518
  const bestMatch = findBestMatch(command, validCommands);
2320
2519
  let out = `
2321
- \u274C Comando inv\xE1lido: "${command}"
2520
+ \u274C Comando invalido: "${command}"
2322
2521
 
2323
2522
  `;
2324
2523
  if (bestMatch) {
2325
- out += `\u{1F4A1} Voc\xEA quis dizer?
2524
+ out += `\u{1F4A1} Voce quis dizer?
2326
2525
  `;
2327
- out += ` \u2192 ai-tool ${bestMatch}
2526
+ out += ` \u2192 ${hint(bestMatch, ctx)}
2328
2527
 
2329
2528
  `;
2330
2529
  }
2331
- out += `\u{1F4CC} Comandos dispon\xEDveis:
2530
+ out += `\u{1F4CC} Comandos disponiveis:
2332
2531
  `;
2333
- for (const [cmd, desc] of Object.entries(COMMAND_REFERENCE)) {
2334
- out += ` ai-tool ${cmd.padEnd(10)} ${desc}
2532
+ for (const cmd of COMMAND_REFERENCE_KEYS) {
2533
+ const desc = COMMAND_DESCRIPTIONS[cmd];
2534
+ out += ` \u2192 ${hint(cmd, ctx)} - ${desc}
2335
2535
  `;
2336
2536
  }
2337
2537
  return out;
@@ -2387,10 +2587,10 @@ function findTargetFile(target, allFiles) {
2387
2587
  }
2388
2588
 
2389
2589
  // src/integrations/git.ts
2390
- import { existsSync as existsSync5 } from "fs";
2590
+ import { existsSync as existsSync6 } from "fs";
2391
2591
  import { execSync as execSync2 } from "child_process";
2392
2592
  function hasGitRepo(cwd) {
2393
- return existsSync5(cwd + "/.git");
2593
+ return existsSync6(cwd + "/.git");
2394
2594
  }
2395
2595
  async function getCommitsForFile(filePath, cwd, limit = 10) {
2396
2596
  if (!hasGitRepo(cwd)) {
@@ -2429,6 +2629,7 @@ async function getCommitsForFile(filePath, cwd, limit = 10) {
2429
2629
  async function impact(target, options = {}) {
2430
2630
  const { cwd, format } = parseCommandOptions(options);
2431
2631
  const useCache = options.cache !== false;
2632
+ const ctx = options.ctx || "cli";
2432
2633
  if (!target) {
2433
2634
  throw new Error("Target \xE9 obrigat\xF3rio. Exemplo: ai-tool impact src/components/Button.tsx");
2434
2635
  }
@@ -2474,7 +2675,7 @@ async function impact(target, options = {}) {
2474
2675
  }
2475
2676
  const targetPath = findTargetFile(target, allFiles);
2476
2677
  if (!targetPath) {
2477
- return formatFileNotFound({ target, allFiles, command: "impact" });
2678
+ return formatFileNotFound({ target, allFiles, command: "impact", ctx });
2478
2679
  }
2479
2680
  const { directUpstream, indirectUpstream, directDownstream, indirectDownstream } = calculateDependencies(targetPath, graph);
2480
2681
  const dependingOn = [...directUpstream, ...indirectUpstream];
@@ -2507,7 +2708,7 @@ async function impact(target, options = {}) {
2507
2708
  suggestions,
2508
2709
  gitHistory
2509
2710
  };
2510
- return formatOutput(result, format, formatImpactText, fromCache);
2711
+ return formatOutput(result, format, (r) => formatImpactText(r, ctx), fromCache);
2511
2712
  } catch (error) {
2512
2713
  const message = error instanceof Error ? error.message : String(error);
2513
2714
  throw new Error(`Erro ao executar impact: ${message}`);
@@ -2669,6 +2870,7 @@ async function suggest(target, options = {}) {
2669
2870
  const { cwd, format } = parseCommandOptions(options);
2670
2871
  const useCache = options.cache !== false;
2671
2872
  const limit = options.limit || 10;
2873
+ const ctx = options.ctx || "cli";
2672
2874
  if (!target) {
2673
2875
  throw new Error("Target e obrigatorio. Exemplo: ai-tool suggest src/components/Button.tsx");
2674
2876
  }
@@ -2710,7 +2912,7 @@ async function suggest(target, options = {}) {
2710
2912
  }
2711
2913
  const targetPath = findTargetFile(target, allFiles);
2712
2914
  if (!targetPath) {
2713
- return formatFileNotFound({ target, allFiles, command: "suggest" });
2915
+ return formatFileNotFound({ target, allFiles, command: "suggest", ctx });
2714
2916
  }
2715
2917
  const suggestions = collectSuggestions(targetPath, graph, allFiles, limit);
2716
2918
  const testSuggestions = generateTestSuggestions(suggestions, allFiles);
@@ -2722,7 +2924,7 @@ async function suggest(target, options = {}) {
2722
2924
  suggestions,
2723
2925
  testSuggestions
2724
2926
  };
2725
- return formatOutput(result, format, formatSuggestText, fromCache);
2927
+ return formatOutput(result, format, (r) => formatSuggestText(r, ctx), fromCache);
2726
2928
  } catch (error) {
2727
2929
  const message = error instanceof Error ? error.message : String(error);
2728
2930
  throw new Error(`Erro ao executar suggest: ${message}`);
@@ -2917,8 +3119,9 @@ function generateTestSuggestions(suggestions, allFiles) {
2917
3119
  }
2918
3120
 
2919
3121
  // src/commands/context.ts
2920
- import { existsSync as existsSync6, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
2921
- import { join as join6, resolve as resolve2, basename, extname as extname3 } from "path";
3122
+ import { existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
3123
+ import { join as join7, resolve as resolve2, basename, extname as extname3 } from "path";
3124
+ import { SyntaxKind as SyntaxKind3 } from "ts-morph";
2922
3125
 
2923
3126
  // src/ts/extractor.ts
2924
3127
  import { Project, SyntaxKind } from "ts-morph";
@@ -3153,7 +3356,7 @@ import { SyntaxKind as SyntaxKind2 } from "ts-morph";
3153
3356
 
3154
3357
  // src/ts/index.ts
3155
3358
  import { readdirSync as readdirSync2, statSync as statSync2 } from "fs";
3156
- import { join as join5, extname as extname2 } from "path";
3359
+ import { join as join6, extname as extname2 } from "path";
3157
3360
  import { Project as Project2 } from "ts-morph";
3158
3361
 
3159
3362
  // src/utils/logger.ts
@@ -3268,7 +3471,7 @@ function getAllCodeFiles(dir, files = [], baseDir = dir) {
3268
3471
  try {
3269
3472
  const entries = readdirSync2(dir);
3270
3473
  for (const entry of entries) {
3271
- const fullPath = join5(dir, entry);
3474
+ const fullPath = join6(dir, entry);
3272
3475
  if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) {
3273
3476
  continue;
3274
3477
  }
@@ -3784,6 +3987,7 @@ function indexProject(cwd) {
3784
3987
  // src/commands/context.ts
3785
3988
  async function context(target, options = {}) {
3786
3989
  const { cwd, format } = parseCommandOptions(options);
3990
+ const ctx = options.ctx || "cli";
3787
3991
  if (!target) {
3788
3992
  throw new Error("Target e obrigatorio. Exemplo: ai-tool context src/components/Button.tsx");
3789
3993
  }
@@ -3791,7 +3995,7 @@ async function context(target, options = {}) {
3791
3995
  const targetPath = findTargetFile2(target, cwd);
3792
3996
  if (!targetPath) {
3793
3997
  const allFiles = getAllCodeFiles2(cwd);
3794
- return formatFileNotFound({ target, allFiles, command: "context" });
3998
+ return formatFileNotFound({ target, allFiles, command: "context", ctx });
3795
3999
  }
3796
4000
  const project = createProject(cwd);
3797
4001
  const absolutePath = resolve2(cwd, targetPath);
@@ -3800,6 +4004,24 @@ async function context(target, options = {}) {
3800
4004
  const functions2 = extractFunctions(sourceFile);
3801
4005
  const types = extractTypes(sourceFile);
3802
4006
  const exports = extractExports(sourceFile);
4007
+ const constants = [];
4008
+ for (const varStatement of sourceFile.getVariableStatements()) {
4009
+ if (!varStatement.isExported()) continue;
4010
+ for (const decl of varStatement.getDeclarations()) {
4011
+ const init = decl.getInitializer();
4012
+ if (init) {
4013
+ const kind = init.getKind();
4014
+ if (kind === SyntaxKind3.ArrowFunction || kind === SyntaxKind3.FunctionExpression) {
4015
+ continue;
4016
+ }
4017
+ }
4018
+ constants.push({
4019
+ name: decl.getName(),
4020
+ type: simplifyType(decl.getType().getText()),
4021
+ isExported: true
4022
+ });
4023
+ }
4024
+ }
3803
4025
  const result = {
3804
4026
  version: "1.0.0",
3805
4027
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -3808,9 +4030,10 @@ async function context(target, options = {}) {
3808
4030
  imports,
3809
4031
  exports,
3810
4032
  functions: functions2,
3811
- types
4033
+ types,
4034
+ constants: constants.length > 0 ? constants : void 0
3812
4035
  };
3813
- return formatOutput(result, format, formatContextText);
4036
+ return formatOutput(result, format, (r) => formatContextText(r, ctx));
3814
4037
  } catch (error) {
3815
4038
  const message = error instanceof Error ? error.message : String(error);
3816
4039
  throw new Error(`Erro ao executar context: ${message}`);
@@ -3820,12 +4043,12 @@ var CODE_EXTENSIONS3 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".
3820
4043
  function findTargetFile2(target, cwd) {
3821
4044
  const normalizedTarget = target.replace(/\\/g, "/");
3822
4045
  const directPath = resolve2(cwd, normalizedTarget);
3823
- if (existsSync6(directPath) && isCodeFile2(directPath)) {
4046
+ if (existsSync7(directPath) && isCodeFile2(directPath)) {
3824
4047
  return normalizedTarget;
3825
4048
  }
3826
4049
  for (const ext of CODE_EXTENSIONS3) {
3827
4050
  const withExt = directPath + ext;
3828
- if (existsSync6(withExt)) {
4051
+ if (existsSync7(withExt)) {
3829
4052
  return normalizedTarget + ext;
3830
4053
  }
3831
4054
  }
@@ -3855,7 +4078,7 @@ function getAllCodeFiles2(dir, files = [], baseDir = dir) {
3855
4078
  try {
3856
4079
  const entries = readdirSync3(dir);
3857
4080
  for (const entry of entries) {
3858
- const fullPath = join6(dir, entry);
4081
+ const fullPath = join7(dir, entry);
3859
4082
  if (shouldIgnore(entry)) {
3860
4083
  continue;
3861
4084
  }
@@ -3892,10 +4115,32 @@ async function areaContext(areaName, options = {}) {
3892
4115
  const cwd = options.cwd || process.cwd();
3893
4116
  const format = options.format || "text";
3894
4117
  const useCache = options.cache !== false;
4118
+ const ctx = options.ctx || "cli";
3895
4119
  if (!areaName) {
3896
4120
  throw new Error("Nome da \xE1rea \xE9 obrigat\xF3rio. Exemplo: ai-tool context --area=auth");
3897
4121
  }
3898
4122
  try {
4123
+ if (!configExists(cwd) || Object.keys(readConfig(cwd).areas).length === 0) {
4124
+ let out = `\u26A0\uFE0F Nenhuma area configurada neste projeto.
4125
+
4126
+ `;
4127
+ out += `O comando area_context requer areas configuradas em .analyze/areas.config.json
4128
+ `;
4129
+ out += `Para configurar:
4130
+ `;
4131
+ out += ` 1. ${hint("areas_init", ctx)} - gerar arquivo de configuracao
4132
+ `;
4133
+ out += ` 2. Edite .analyze/areas.config.json com as areas do projeto
4134
+
4135
+ `;
4136
+ out += `Enquanto isso, use:
4137
+ `;
4138
+ out += ` \u2192 ${hint("context", ctx)} - ver assinaturas de um arquivo especifico
4139
+ `;
4140
+ out += ` \u2192 ${hint("find", ctx)} - buscar simbolos no codigo
4141
+ `;
4142
+ return out;
4143
+ }
3899
4144
  const config = readConfig(cwd);
3900
4145
  let index;
3901
4146
  if (useCache && isCacheValid(cwd)) {
@@ -3927,9 +4172,19 @@ async function areaContext(areaName, options = {}) {
3927
4172
  }
3928
4173
  }
3929
4174
  if (areaFiles.length === 0) {
3930
- return format === "json" ? JSON.stringify({ error: `\xC1rea n\xE3o encontrada: "${areaName}"` }) : `\u274C \xC1rea n\xE3o encontrada: "${areaName}"
3931
-
3932
- \u{1F4A1} Use 'ai-tool areas' para listar \xE1reas dispon\xEDveis`;
4175
+ const availableAreas = /* @__PURE__ */ new Map();
4176
+ for (const filePath of Object.keys(index.files)) {
4177
+ if (isFileIgnored(filePath, config)) continue;
4178
+ const fileAreas = detectFileAreas(filePath, config);
4179
+ for (const areaId of fileAreas) {
4180
+ availableAreas.set(areaId, (availableAreas.get(areaId) || 0) + 1);
4181
+ }
4182
+ }
4183
+ const areaList = [...availableAreas.entries()].map(([id, count]) => ({ id, count })).sort((a, b) => b.count - a.count);
4184
+ if (format === "json") {
4185
+ return JSON.stringify({ error: `Area nao encontrada: "${areaName}"`, availableAreas: areaList });
4186
+ }
4187
+ return formatAreaNotFound({ target: areaName, availableAreas: areaList, ctx });
3933
4188
  }
3934
4189
  const types = [];
3935
4190
  const hooks = [];
@@ -4035,7 +4290,7 @@ async function areaContext(areaName, options = {}) {
4035
4290
  if (format === "json") {
4036
4291
  return JSON.stringify(result, null, 2);
4037
4292
  }
4038
- return formatAreaContextText(result);
4293
+ return formatAreaContextText(result, ctx);
4039
4294
  } catch (error) {
4040
4295
  const message = error instanceof Error ? error.message : String(error);
4041
4296
  throw new Error(`Erro ao executar context --area: ${message}`);
@@ -4062,7 +4317,7 @@ function findRealAreaIdFromIndex(target, areaFiles, config) {
4062
4317
 
4063
4318
  // src/commands/areas.ts
4064
4319
  import { readdirSync as readdirSync4, statSync as statSync4 } from "fs";
4065
- import { join as join7, extname as extname4 } from "path";
4320
+ import { join as join8, extname as extname4 } from "path";
4066
4321
  var CODE_EXTENSIONS4 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
4067
4322
  var IGNORED_DIRS2 = /* @__PURE__ */ new Set([
4068
4323
  "node_modules",
@@ -4079,6 +4334,7 @@ var IGNORED_DIRS2 = /* @__PURE__ */ new Set([
4079
4334
  async function areas(options = {}) {
4080
4335
  const cwd = options.cwd || process.cwd();
4081
4336
  const format = options.format || "text";
4337
+ const ctx = options.ctx || "cli";
4082
4338
  try {
4083
4339
  const config = readConfig(cwd);
4084
4340
  const allFiles = getAllCodeFiles3(cwd);
@@ -4140,7 +4396,7 @@ async function areas(options = {}) {
4140
4396
  if (format === "json") {
4141
4397
  return JSON.stringify(result, null, 2);
4142
4398
  }
4143
- return formatAreasText(result);
4399
+ return formatAreasText(result, ctx);
4144
4400
  } catch (error) {
4145
4401
  const message = error instanceof Error ? error.message : String(error);
4146
4402
  throw new Error(`Erro ao executar areas: ${message}`);
@@ -4150,7 +4406,7 @@ function getAllCodeFiles3(dir, files = [], baseDir = dir) {
4150
4406
  try {
4151
4407
  const entries = readdirSync4(dir);
4152
4408
  for (const entry of entries) {
4153
- const fullPath = join7(dir, entry);
4409
+ const fullPath = join8(dir, entry);
4154
4410
  if (IGNORED_DIRS2.has(entry) || entry.startsWith(".")) {
4155
4411
  continue;
4156
4412
  }
@@ -4175,7 +4431,7 @@ function getAllCodeFiles3(dir, files = [], baseDir = dir) {
4175
4431
 
4176
4432
  // src/commands/area.ts
4177
4433
  import { readdirSync as readdirSync5, statSync as statSync5 } from "fs";
4178
- import { join as join8, extname as extname5 } from "path";
4434
+ import { join as join9, extname as extname5 } from "path";
4179
4435
  var CODE_EXTENSIONS5 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
4180
4436
  var IGNORED_DIRS3 = /* @__PURE__ */ new Set([
4181
4437
  "node_modules",
@@ -4224,10 +4480,32 @@ async function area(target, options = {}) {
4224
4480
  const format = options.format || "text";
4225
4481
  const filterType = options.type;
4226
4482
  const full = options.full ?? false;
4483
+ const ctx = options.ctx || "cli";
4227
4484
  if (!target) {
4228
4485
  throw new Error("Nome da \xE1rea \xE9 obrigat\xF3rio. Exemplo: ai-tool area auth");
4229
4486
  }
4230
4487
  try {
4488
+ if (!configExists(cwd) || Object.keys(readConfig(cwd).areas).length === 0) {
4489
+ let out = `\u26A0\uFE0F Nenhuma area configurada neste projeto.
4490
+
4491
+ `;
4492
+ out += `O comando area requer areas configuradas em .analyze/areas.config.json
4493
+ `;
4494
+ out += `Para configurar:
4495
+ `;
4496
+ out += ` 1. ${hint("areas_init", ctx)} - gerar arquivo de configuracao
4497
+ `;
4498
+ out += ` 2. Edite .analyze/areas.config.json com as areas do projeto
4499
+
4500
+ `;
4501
+ out += `Enquanto isso, use:
4502
+ `;
4503
+ out += ` \u2192 ${hint("map", ctx)} - ver estrutura do projeto
4504
+ `;
4505
+ out += ` \u2192 ${hint("find", ctx)} - buscar simbolos no codigo
4506
+ `;
4507
+ return out;
4508
+ }
4231
4509
  const config = readConfig(cwd);
4232
4510
  const allFiles = getAllCodeFiles4(cwd);
4233
4511
  const resolvedTarget = resolveAreaId(target, config, allFiles);
@@ -4257,7 +4535,7 @@ async function area(target, options = {}) {
4257
4535
  }
4258
4536
  if (areaFiles.length === 0) {
4259
4537
  const availableAreas = getAvailableAreas(filteredFiles, config);
4260
- return formatAreaNotFound2(target, availableAreas);
4538
+ return formatAreaNotFound2(target, availableAreas, ctx);
4261
4539
  }
4262
4540
  const byCategory = {};
4263
4541
  const categories = {};
@@ -4294,7 +4572,7 @@ async function area(target, options = {}) {
4294
4572
  if (format === "json") {
4295
4573
  return JSON.stringify(result, null, 2);
4296
4574
  }
4297
- const output = formatAreaDetailText(result, { full, filterType });
4575
+ const output = formatAreaDetailText(result, { full, filterType }, ctx);
4298
4576
  return nameConversionMsg + output;
4299
4577
  } catch (error) {
4300
4578
  const message = error instanceof Error ? error.message : String(error);
@@ -4329,14 +4607,14 @@ function getAvailableAreas(allFiles, config) {
4329
4607
  }
4330
4608
  return [...areaCounts.entries()].map(([id, count]) => ({ id, count })).sort((a, b) => b.count - a.count);
4331
4609
  }
4332
- function formatAreaNotFound2(target, availableAreas) {
4333
- return formatAreaNotFound({ target, availableAreas });
4610
+ function formatAreaNotFound2(target, availableAreas, ctx = "cli") {
4611
+ return formatAreaNotFound({ target, availableAreas, ctx });
4334
4612
  }
4335
4613
  function getAllCodeFiles4(dir, files = [], baseDir = dir) {
4336
4614
  try {
4337
4615
  const entries = readdirSync5(dir);
4338
4616
  for (const entry of entries) {
4339
- const fullPath = join8(dir, entry);
4617
+ const fullPath = join9(dir, entry);
4340
4618
  if (IGNORED_DIRS3.has(entry) || entry.startsWith(".")) {
4341
4619
  continue;
4342
4620
  }
@@ -4361,7 +4639,7 @@ function getAllCodeFiles4(dir, files = [], baseDir = dir) {
4361
4639
 
4362
4640
  // src/commands/areas-init.ts
4363
4641
  import { readdirSync as readdirSync6, statSync as statSync6 } from "fs";
4364
- import { join as join9, extname as extname6 } from "path";
4642
+ import { join as join10, extname as extname6 } from "path";
4365
4643
  var CODE_EXTENSIONS6 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
4366
4644
  var IGNORED_DIRS4 = /* @__PURE__ */ new Set([
4367
4645
  "node_modules",
@@ -4377,7 +4655,7 @@ var IGNORED_DIRS4 = /* @__PURE__ */ new Set([
4377
4655
  ]);
4378
4656
  function createInitialConfig(files) {
4379
4657
  const suggestedIgnore = detectSuggestedIgnorePatterns(files);
4380
- const framework = detectFramework(files);
4658
+ const framework = detectFramework2(files);
4381
4659
  return {
4382
4660
  $schema: "./areas.schema.json",
4383
4661
  version: "1.0.0",
@@ -4391,7 +4669,7 @@ function createInitialConfig(files) {
4391
4669
  }
4392
4670
  };
4393
4671
  }
4394
- function detectFramework(files) {
4672
+ function detectFramework2(files) {
4395
4673
  const hasAppDir = files.some((f) => f.startsWith("app/"));
4396
4674
  const hasPagesDir = files.some((f) => f.startsWith("pages/"));
4397
4675
  const hasSrcDir = files.some((f) => f.startsWith("src/"));
@@ -4546,7 +4824,7 @@ Ou edite manualmente o arquivo existente.
4546
4824
  const allFiles = getAllCodeFiles5(cwd);
4547
4825
  const newConfig = createInitialConfig(allFiles);
4548
4826
  writeConfig(cwd, newConfig);
4549
- const framework = detectFramework(allFiles);
4827
+ const framework = detectFramework2(allFiles);
4550
4828
  const frameworkName = {
4551
4829
  "nextjs-app": "Next.js (App Router)",
4552
4830
  "nextjs-pages": "Next.js (Pages Router)",
@@ -4650,7 +4928,7 @@ function getAllCodeFiles5(dir, files = [], baseDir = dir) {
4650
4928
  try {
4651
4929
  const entries = readdirSync6(dir);
4652
4930
  for (const entry of entries) {
4653
- const fullPath = join9(dir, entry);
4931
+ const fullPath = join10(dir, entry);
4654
4932
  if (IGNORED_DIRS4.has(entry) || entry.startsWith(".")) {
4655
4933
  continue;
4656
4934
  }
@@ -4681,13 +4959,42 @@ async function functions(options = {}) {
4681
4959
  const format = options.format || "text";
4682
4960
  const useCache = options.cache !== false;
4683
4961
  const filterTrigger = options.trigger;
4962
+ const ctx = options.ctx || "cli";
4684
4963
  if (!isFirebaseProject(cwd)) {
4685
- const errorMsg = "Este n\xE3o \xE9 um projeto Firebase.\nN\xE3o foi encontrado .firebaserc ou firebase.json";
4686
- return format === "json" ? JSON.stringify({ error: errorMsg }) : `\u274C ${errorMsg}`;
4964
+ const errorMsg = "Este nao e um projeto Firebase (nao encontrou .firebaserc ou firebase.json)";
4965
+ if (format === "json") {
4966
+ return JSON.stringify({ error: errorMsg });
4967
+ }
4968
+ let out = `\u274C ${errorMsg}
4969
+ `;
4970
+ out += `
4971
+ \u{1F4A1} Comandos disponiveis para este projeto:
4972
+ `;
4973
+ out += ` \u2192 ${hint("map", ctx)} - ver estrutura do projeto
4974
+ `;
4975
+ out += ` \u2192 ${hint("find", ctx)} - buscar simbolos no codigo
4976
+ `;
4977
+ out += ` \u2192 ${hint("areas", ctx)} - listar areas funcionais
4978
+ `;
4979
+ return out;
4687
4980
  }
4688
4981
  if (!hasFirebaseFunctions(cwd)) {
4689
- const errorMsg = "Projeto Firebase sem Cloud Functions.\nN\xE3o foi encontrado functions/src/index.ts";
4690
- return format === "json" ? JSON.stringify({ error: errorMsg }) : `\u274C ${errorMsg}`;
4982
+ const errorMsg = "Projeto Firebase detectado, mas sem Cloud Functions";
4983
+ if (format === "json") {
4984
+ return JSON.stringify({ error: errorMsg });
4985
+ }
4986
+ let out = `\u274C ${errorMsg}
4987
+ `;
4988
+ out += ` Nao foi encontrado functions/src/index.ts
4989
+
4990
+ `;
4991
+ out += `\u{1F4A1} Para adicionar Cloud Functions:
4992
+ `;
4993
+ out += ` \u2192 firebase init functions
4994
+ `;
4995
+ out += ` \u2192 Documentacao: https://firebase.google.com/docs/functions
4996
+ `;
4997
+ return out;
4691
4998
  }
4692
4999
  try {
4693
5000
  let index;
@@ -4754,13 +5061,13 @@ async function functions(options = {}) {
4754
5061
  if (format === "json") {
4755
5062
  return JSON.stringify(result, null, 2);
4756
5063
  }
4757
- return formatFunctionsText(result);
5064
+ return formatFunctionsText(result, ctx);
4758
5065
  } catch (error) {
4759
5066
  const message = error instanceof Error ? error.message : String(error);
4760
5067
  throw new Error(`Erro ao executar functions: ${message}`);
4761
5068
  }
4762
5069
  }
4763
- function formatFunctionsText(result) {
5070
+ function formatFunctionsText(result, ctx = "cli") {
4764
5071
  let out = "";
4765
5072
  out += `
4766
5073
  `;
@@ -4777,42 +5084,35 @@ function formatFunctionsText(result) {
4777
5084
  `;
4778
5085
  out += ` Exportadas: ${result.summary.exported}
4779
5086
  `;
4780
- if (result.summary.total > 0) {
4781
- out += `
4782
- \u{1F4A1} Filtros dispon\xEDveis:
4783
- `;
4784
- out += ` ai-tool functions --trigger=onCall
4785
- `;
4786
- out += ` ai-tool functions --trigger=onDocumentCreated
4787
- `;
4788
- }
4789
5087
  out += `
4790
5088
  `;
4791
5089
  if (result.summary.total === 0) {
4792
5090
  out += ` \u26A0\uFE0F NENHUMA CLOUD FUNCTION DETECTADA
4793
5091
 
4794
5092
  `;
4795
- out += ` Poss\xEDveis causas:
4796
- `;
4797
- out += ` 1. O projeto n\xE3o \xE9 Firebase (n\xE3o encontrou .firebaserc ou firebase.json)
5093
+ out += ` Possiveis causas:
4798
5094
  `;
4799
- out += ` 2. N\xE3o h\xE1 arquivo functions/src/index.ts
5095
+ out += ` 1. O projeto nao e Firebase (nao encontrou .firebaserc ou firebase.json)
4800
5096
  `;
4801
- out += ` 3. Os triggers n\xE3o usam padr\xF5es v2 (onCall, onDocumentCreated, etc)
5097
+ out += ` 2. Nao ha arquivo functions/src/index.ts
4802
5098
  `;
4803
- out += ` 4. O cache est\xE1 desatualizado (ex: atualizou o ai-tool recentemente)
5099
+ out += ` 3. Os triggers nao usam padroes v2 (onCall, onDocumentCreated, etc)
4804
5100
  `;
4805
- out += ` \u2192 Tente: ai-tool functions --no-cache
5101
+ out += ` 4. O cache esta desatualizado
4806
5102
 
4807
5103
  `;
4808
- out += ` Padr\xF5es suportados:
5104
+ out += ` Padroes suportados:
4809
5105
  `;
4810
5106
  out += ` export const minhaFunc = onCall((request) => { ... })
4811
5107
  `;
4812
5108
  out += ` export const minhaFunc = onDocumentCreated("path", (event) => { ... })
4813
5109
 
4814
5110
  `;
4815
- out += ` Documenta\xE7\xE3o: https://firebase.google.com/docs/functions
5111
+ out += `\u{1F4A1} Dicas:
5112
+ `;
5113
+ out += ` \u2192 ${hint("map", ctx)} - ver estrutura do projeto
5114
+ `;
5115
+ out += ` \u2192 ${hint("find", ctx)} - buscar simbolos no codigo
4816
5116
  `;
4817
5117
  return out;
4818
5118
  }
@@ -4882,6 +5182,7 @@ function formatFunctionsText(result) {
4882
5182
  out += `
4883
5183
  `;
4884
5184
  }
5185
+ out += nextSteps("functions", ctx);
4885
5186
  return out;
4886
5187
  }
4887
5188
  function getTriggerIcon(trigger) {
@@ -4899,6 +5200,8 @@ function getTriggerIcon(trigger) {
4899
5200
  }
4900
5201
 
4901
5202
  // src/commands/find.ts
5203
+ import { readFileSync as readFileSync4 } from "fs";
5204
+ import { join as join11 } from "path";
4902
5205
  async function find(query, options = {}) {
4903
5206
  const { cwd, format } = parseCommandOptions(options);
4904
5207
  const filterType = options.type || "all";
@@ -4906,6 +5209,7 @@ async function find(query, options = {}) {
4906
5209
  const defOnly = options.def ?? false;
4907
5210
  const refsOnly = options.refs ?? false;
4908
5211
  const useCache = options.cache !== false;
5212
+ const ctx = options.ctx || "cli";
4909
5213
  const listAllMode = !query && defOnly && filterType && filterType !== "all";
4910
5214
  if (!query && !listAllMode) {
4911
5215
  throw new Error("Query \xE9 obrigat\xF3ria. Exemplo: ai-tool find useAuth\n Ou use --def para listar todos de um tipo: ai-tool find --type=trigger --def");
@@ -4946,10 +5250,22 @@ async function find(query, options = {}) {
4946
5250
  }
4947
5251
  }
4948
5252
  if (allowedFiles.size === 0) {
4949
- return format === "json" ? JSON.stringify({ error: `Nenhum arquivo encontrado na \xE1rea "${filterArea}"` }) : `\u274C Nenhum arquivo encontrado na \xE1rea "${filterArea}"`;
5253
+ const availableAreas = /* @__PURE__ */ new Map();
5254
+ for (const filePath of Object.keys(index.files)) {
5255
+ if (isFileIgnored(filePath, config)) continue;
5256
+ const fileAreas = detectFileAreas(filePath, config);
5257
+ for (const areaId of fileAreas) {
5258
+ availableAreas.set(areaId, (availableAreas.get(areaId) || 0) + 1);
5259
+ }
5260
+ }
5261
+ const areaList = [...availableAreas.entries()].map(([id, count]) => ({ id, count })).sort((a, b) => b.count - a.count);
5262
+ if (format === "json") {
5263
+ return JSON.stringify({ error: `Nenhum arquivo encontrado na area "${filterArea}"`, availableAreas: areaList });
5264
+ }
5265
+ return formatAreaNotFound({ target: filterArea, availableAreas: areaList, ctx });
4950
5266
  }
4951
5267
  }
4952
- const matches = searchInIndex(index, query, filterType, allowedFiles);
5268
+ const matches = searchInIndex(index, query, filterType, allowedFiles, cwd);
4953
5269
  let definition = null;
4954
5270
  let references = [];
4955
5271
  for (const match of matches) {
@@ -4991,13 +5307,14 @@ async function find(query, options = {}) {
4991
5307
  },
4992
5308
  fromCache
4993
5309
  };
4994
- return formatOutput(result, format, formatFindText, fromCache);
5310
+ const allSymbolNames = Object.keys(index.symbolsByName);
5311
+ return formatOutput(result, format, (r) => formatFindText(r, ctx, allSymbolNames), fromCache);
4995
5312
  } catch (error) {
4996
5313
  const message = error instanceof Error ? error.message : String(error);
4997
5314
  throw new Error(`Erro ao executar find: ${message}`);
4998
5315
  }
4999
5316
  }
5000
- function searchInIndex(index, query, filterType, allowedFiles) {
5317
+ function searchInIndex(index, query, filterType, allowedFiles, cwd) {
5001
5318
  const matches = [];
5002
5319
  const queryLower = query?.toLowerCase() || "";
5003
5320
  const processedSymbols = /* @__PURE__ */ new Set();
@@ -5050,6 +5367,45 @@ function searchInIndex(index, query, filterType, allowedFiles) {
5050
5367
  }
5051
5368
  }
5052
5369
  }
5370
+ if (!listAllMode && query) {
5371
+ const importFiles = matches.filter((m) => m.matchType === "import").map((m) => m.file);
5372
+ const baseCwd = cwd || "";
5373
+ const MAX_USAGE_FILES = 10;
5374
+ const MAX_USAGES_PER_FILE = 3;
5375
+ for (const filePath of importFiles.slice(0, MAX_USAGE_FILES)) {
5376
+ try {
5377
+ const fullPath = baseCwd ? join11(baseCwd, filePath) : filePath;
5378
+ const content = readFileSync4(fullPath, "utf-8");
5379
+ const lines = content.split("\n");
5380
+ const usageRegex = new RegExp(`\\b${escapeRegex(query)}\\b`);
5381
+ let usagesFound = 0;
5382
+ for (let i = 0; i < lines.length && usagesFound < MAX_USAGES_PER_FILE; i++) {
5383
+ const line = lines[i].trim();
5384
+ if (line.startsWith("import ") || line.startsWith("export ") || line.startsWith("//") || line.startsWith("*") || line.startsWith("/*")) {
5385
+ continue;
5386
+ }
5387
+ if (usageRegex.test(line)) {
5388
+ const key = `usage:${filePath}:${i + 1}:${query}`;
5389
+ if (processedSymbols.has(key)) continue;
5390
+ processedSymbols.add(key);
5391
+ const fileData = index.files[filePath];
5392
+ const codeLine = line.length > 100 ? line.substring(0, 100) + "..." : line;
5393
+ matches.push({
5394
+ file: filePath,
5395
+ line: i + 1,
5396
+ column: 0,
5397
+ code: codeLine,
5398
+ matchType: "usage",
5399
+ symbolType: inferSymbolTypeFromName(query),
5400
+ category: fileData?.category || "other"
5401
+ });
5402
+ usagesFound++;
5403
+ }
5404
+ }
5405
+ } catch {
5406
+ }
5407
+ }
5408
+ }
5053
5409
  matches.sort((a, b) => {
5054
5410
  const order = { definition: 0, import: 1, usage: 2 };
5055
5411
  const orderDiff = order[a.matchType] - order[b.matchType];
@@ -5058,6 +5414,9 @@ function searchInIndex(index, query, filterType, allowedFiles) {
5058
5414
  });
5059
5415
  return matches;
5060
5416
  }
5417
+ function escapeRegex(str) {
5418
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5419
+ }
5061
5420
  function matchesType(kind, filter) {
5062
5421
  if (filter === "all") return true;
5063
5422
  switch (filter) {
@@ -5118,6 +5477,13 @@ export {
5118
5477
  categoryIcons,
5119
5478
  isEntryPoint,
5120
5479
  isCodeFile,
5480
+ hint,
5481
+ nextSteps,
5482
+ recoveryHint,
5483
+ levenshteinDistance,
5484
+ findSimilar,
5485
+ findBestMatch,
5486
+ extractFileName,
5121
5487
  isFirebaseProject,
5122
5488
  hasFirebaseFunctions,
5123
5489
  isExportedCloudFunction,
@@ -5146,11 +5512,6 @@ export {
5146
5512
  map,
5147
5513
  dead,
5148
5514
  deadFix,
5149
- levenshteinDistance,
5150
- findSimilar,
5151
- findBestMatch,
5152
- extractFileName,
5153
- COMMAND_REFERENCE,
5154
5515
  formatFileNotFound,
5155
5516
  formatAreaNotFound,
5156
5517
  formatMissingTarget,