@justmpm/ai-tool 0.5.2 → 0.5.4

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.
@@ -1873,6 +1873,254 @@ ${output}`;
1873
1873
 
1874
1874
  // src/commands/impact.ts
1875
1875
  import skott2 from "skott";
1876
+
1877
+ // src/utils/similarity.ts
1878
+ function levenshteinDistance(a, b) {
1879
+ const matrix = [];
1880
+ for (let i = 0; i <= b.length; i++) {
1881
+ matrix[i] = [i];
1882
+ }
1883
+ for (let j = 0; j <= a.length; j++) {
1884
+ matrix[0][j] = j;
1885
+ }
1886
+ for (let i = 1; i <= b.length; i++) {
1887
+ for (let j = 1; j <= a.length; j++) {
1888
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
1889
+ matrix[i][j] = matrix[i - 1][j - 1];
1890
+ } else {
1891
+ matrix[i][j] = Math.min(
1892
+ matrix[i - 1][j - 1] + 1,
1893
+ // substituição
1894
+ matrix[i][j - 1] + 1,
1895
+ // inserção
1896
+ matrix[i - 1][j] + 1
1897
+ // deleção
1898
+ );
1899
+ }
1900
+ }
1901
+ }
1902
+ return matrix[b.length][a.length];
1903
+ }
1904
+ function findSimilar(target, candidates, options = {}) {
1905
+ const {
1906
+ maxDistance = 3,
1907
+ limit = 5,
1908
+ normalize: normalize2 = true,
1909
+ extractKey = (item) => String(item)
1910
+ } = options;
1911
+ const normalizedTarget = normalize2 ? target.toLowerCase() : target;
1912
+ const scored = candidates.map((item) => {
1913
+ const key = extractKey(item);
1914
+ const normalizedKey = normalize2 ? key.toLowerCase() : key;
1915
+ const keyNoExt = normalizedKey.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
1916
+ let score = Infinity;
1917
+ if (normalizedKey.includes(normalizedTarget) || keyNoExt.includes(normalizedTarget)) {
1918
+ score = 0;
1919
+ } else {
1920
+ score = levenshteinDistance(keyNoExt, normalizedTarget);
1921
+ }
1922
+ return { item, score };
1923
+ }).filter(({ score }) => score <= maxDistance).sort((a, b) => a.score - b.score).slice(0, limit);
1924
+ return scored.map(({ item }) => item);
1925
+ }
1926
+ function findBestMatch(target, candidates, extractKey = (item) => String(item)) {
1927
+ const similar = findSimilar(target, candidates, {
1928
+ maxDistance: 2,
1929
+ // Mais restritivo para "você quis dizer"
1930
+ limit: 1,
1931
+ extractKey
1932
+ });
1933
+ return similar.length > 0 ? similar[0] : null;
1934
+ }
1935
+ function extractFileName(filePath) {
1936
+ const fileName = filePath.split("/").pop() || filePath;
1937
+ return fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
1938
+ }
1939
+
1940
+ // src/utils/errors.ts
1941
+ var COMMAND_REFERENCE = {
1942
+ map: "Resumo do projeto (sem target)",
1943
+ areas: "Listar todas as \xE1reas (sem target)",
1944
+ area: "Arquivos de uma \xE1rea espec\xEDfica",
1945
+ suggest: "O que ler antes de editar",
1946
+ context: "API/assinaturas de um arquivo",
1947
+ impact: "Quem usa este arquivo",
1948
+ dead: "C\xF3digo morto (sem target)"
1949
+ };
1950
+ function getCommandReferenceSection(excludeCommand) {
1951
+ let out = `
1952
+ \u{1F4CC} Comandos \xFAteis:
1953
+ `;
1954
+ for (const [cmd, desc] of Object.entries(COMMAND_REFERENCE)) {
1955
+ if (cmd !== excludeCommand) {
1956
+ out += ` ai-tool ${cmd.padEnd(10)} ${desc}
1957
+ `;
1958
+ }
1959
+ }
1960
+ return out;
1961
+ }
1962
+ function formatFileNotFound(options) {
1963
+ const { target, allFiles, command } = options;
1964
+ const similarFiles = findSimilar(target, allFiles, {
1965
+ maxDistance: 3,
1966
+ limit: 5,
1967
+ extractKey: extractFileName
1968
+ });
1969
+ const bestMatch = findBestMatch(target, allFiles, extractFileName);
1970
+ let out = `
1971
+ \u274C Arquivo n\xE3o encontrado: "${target}"
1972
+
1973
+ `;
1974
+ out += `\u{1F4CA} Total de arquivos indexados: ${allFiles.length}
1975
+
1976
+ `;
1977
+ if (bestMatch) {
1978
+ out += `\u{1F4A1} Voc\xEA quis dizer?
1979
+ `;
1980
+ out += ` \u2192 ${bestMatch}
1981
+
1982
+ `;
1983
+ }
1984
+ if (similarFiles.length > 1) {
1985
+ out += `\u{1F4DD} Arquivos similares:
1986
+ `;
1987
+ for (const f of similarFiles) {
1988
+ if (f !== bestMatch) {
1989
+ out += ` \u2022 ${f}
1990
+ `;
1991
+ }
1992
+ }
1993
+ out += "\n";
1994
+ }
1995
+ out += `\u{1F4D6} Dicas:
1996
+ `;
1997
+ out += ` \u2022 Use o caminho relativo: src/components/Header.tsx
1998
+ `;
1999
+ out += ` \u2022 Ou apenas o nome do arquivo: Header
2000
+ `;
2001
+ out += ` \u2022 Verifique se o arquivo est\xE1 em uma pasta inclu\xEDda no scan
2002
+ `;
2003
+ if (command) {
2004
+ out += getCommandReferenceSection(command);
2005
+ }
2006
+ return out;
2007
+ }
2008
+ function formatAreaNotFound(options) {
2009
+ const { target, availableAreas } = options;
2010
+ const areaIds = availableAreas.map((a) => a.id);
2011
+ const bestMatchId = findBestMatch(target, areaIds);
2012
+ const similarAreaIds = findSimilar(target, areaIds, {
2013
+ maxDistance: 3,
2014
+ limit: 5
2015
+ });
2016
+ let out = `
2017
+ \u274C \xC1rea n\xE3o encontrada: "${target}"
2018
+
2019
+ `;
2020
+ if (bestMatchId) {
2021
+ out += `\u{1F4A1} Voc\xEA quis dizer?
2022
+ `;
2023
+ out += ` \u2192 ai-tool area ${bestMatchId}
2024
+
2025
+ `;
2026
+ }
2027
+ if (availableAreas.length > 0) {
2028
+ out += `\u{1F4E6} \xC1reas dispon\xEDveis:
2029
+
2030
+ `;
2031
+ if (similarAreaIds.length > 0 && !bestMatchId) {
2032
+ for (const id of similarAreaIds) {
2033
+ const area2 = availableAreas.find((a) => a.id === id);
2034
+ if (area2) {
2035
+ out += ` ${area2.id.padEnd(25)} ${area2.count} arquivos
2036
+ `;
2037
+ }
2038
+ }
2039
+ out += ` ---
2040
+ `;
2041
+ }
2042
+ const areasToShow = similarAreaIds.length > 0 && !bestMatchId ? availableAreas.filter((a) => !similarAreaIds.includes(a.id)).slice(0, 10) : availableAreas.slice(0, 15);
2043
+ for (const { id, count } of areasToShow) {
2044
+ out += ` ${id.padEnd(25)} ${count} arquivos
2045
+ `;
2046
+ }
2047
+ const totalShown = similarAreaIds.length > 0 && !bestMatchId ? similarAreaIds.length + areasToShow.length : areasToShow.length;
2048
+ if (availableAreas.length > totalShown) {
2049
+ out += ` ... e mais ${availableAreas.length - totalShown}
2050
+ `;
2051
+ }
2052
+ out += `
2053
+ `;
2054
+ }
2055
+ out += `\u{1F4D6} Dicas:
2056
+ `;
2057
+ out += ` \u2022 Use o ID exato da \xE1rea (ex: ai-tool area auth)
2058
+ `;
2059
+ out += ` \u2022 Use 'ai-tool areas' para listar todas as \xE1reas
2060
+ `;
2061
+ out += ` \u2022 IDs s\xE3o case-sensitive (Auth \u2260 auth)
2062
+ `;
2063
+ out += `
2064
+ \u{1F4CC} Comandos relacionados:
2065
+ `;
2066
+ out += ` ai-tool areas Listar todas as \xE1reas
2067
+ `;
2068
+ out += ` ai-tool map Ver estrutura do projeto
2069
+ `;
2070
+ return out;
2071
+ }
2072
+ function formatMissingTarget(command) {
2073
+ let out = `
2074
+ \u274C Erro: par\xE2metro "target" \xE9 OBRIGAT\xD3RIO para o comando "${command}".
2075
+
2076
+ `;
2077
+ out += `\u{1F4DD} Exemplos:
2078
+ `;
2079
+ if (command === "area") {
2080
+ out += ` ai-tool area auth
2081
+ `;
2082
+ out += ` ai-tool area dashboard
2083
+ `;
2084
+ out += ` ai-tool area billing --type=hook
2085
+
2086
+ `;
2087
+ out += `\u{1F4A1} Use 'ai-tool areas' para listar todas as \xE1reas dispon\xEDveis.
2088
+ `;
2089
+ } else {
2090
+ out += ` ai-tool ${command} useAuth
2091
+ `;
2092
+ out += ` ai-tool ${command} Button.tsx
2093
+ `;
2094
+ out += ` ai-tool ${command} src/hooks/useAuth.ts
2095
+ `;
2096
+ }
2097
+ out += getCommandReferenceSection(command);
2098
+ return out;
2099
+ }
2100
+ function formatInvalidCommand(command) {
2101
+ const validCommands = Object.keys(COMMAND_REFERENCE);
2102
+ const bestMatch = findBestMatch(command, validCommands);
2103
+ let out = `
2104
+ \u274C Comando inv\xE1lido: "${command}"
2105
+
2106
+ `;
2107
+ if (bestMatch) {
2108
+ out += `\u{1F4A1} Voc\xEA quis dizer?
2109
+ `;
2110
+ out += ` \u2192 ai-tool ${bestMatch}
2111
+
2112
+ `;
2113
+ }
2114
+ out += `\u{1F4CC} Comandos dispon\xEDveis:
2115
+ `;
2116
+ for (const [cmd, desc] of Object.entries(COMMAND_REFERENCE)) {
2117
+ out += ` ai-tool ${cmd.padEnd(10)} ${desc}
2118
+ `;
2119
+ }
2120
+ return out;
2121
+ }
2122
+
2123
+ // src/commands/impact.ts
1876
2124
  async function impact(target, options = {}) {
1877
2125
  const cwd = options.cwd || process.cwd();
1878
2126
  const format = options.format || "text";
@@ -2065,37 +2313,7 @@ function findTargetFile(target, allFiles) {
2065
2313
  return null;
2066
2314
  }
2067
2315
  function formatNotFound(target, allFiles) {
2068
- const normalizedTarget = target.toLowerCase();
2069
- const similar = allFiles.filter((f) => {
2070
- const fileName = f.split("/").pop()?.toLowerCase() || "";
2071
- const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2072
- return fileName.includes(normalizedTarget) || fileNameNoExt.includes(normalizedTarget) || levenshteinDistance(fileNameNoExt, normalizedTarget) <= 3;
2073
- }).slice(0, 5);
2074
- let out = `\u274C Arquivo n\xE3o encontrado no \xEDndice: "${target}"
2075
-
2076
- `;
2077
- out += `\u{1F4CA} Total de arquivos indexados: ${allFiles.length}
2078
-
2079
- `;
2080
- if (similar.length > 0) {
2081
- out += `\u{1F4DD} Arquivos com nome similar:
2082
- `;
2083
- for (const s of similar) {
2084
- out += ` \u2022 ${s}
2085
- `;
2086
- }
2087
- out += `
2088
- `;
2089
- }
2090
- out += `\u{1F4A1} Dicas:
2091
- `;
2092
- out += ` \u2022 Use o caminho relativo: src/components/Header.tsx
2093
- `;
2094
- out += ` \u2022 Ou apenas o nome do arquivo: Header
2095
- `;
2096
- out += ` \u2022 Verifique se o arquivo est\xE1 em uma pasta inclu\xEDda no scan
2097
- `;
2098
- return out;
2316
+ return formatFileNotFound({ target, allFiles, command: "impact" });
2099
2317
  }
2100
2318
  function toImpactFile(isDirect) {
2101
2319
  return (path) => ({
@@ -2164,29 +2382,6 @@ function generateSuggestions(upstream, downstream, risks) {
2164
2382
  }
2165
2383
  return suggestions;
2166
2384
  }
2167
- function levenshteinDistance(a, b) {
2168
- const matrix = [];
2169
- for (let i = 0; i <= b.length; i++) {
2170
- matrix[i] = [i];
2171
- }
2172
- for (let j = 0; j <= a.length; j++) {
2173
- matrix[0][j] = j;
2174
- }
2175
- for (let i = 1; i <= b.length; i++) {
2176
- for (let j = 1; j <= a.length; j++) {
2177
- if (b.charAt(i - 1) === a.charAt(j - 1)) {
2178
- matrix[i][j] = matrix[i - 1][j - 1];
2179
- } else {
2180
- matrix[i][j] = Math.min(
2181
- matrix[i - 1][j - 1] + 1,
2182
- matrix[i][j - 1] + 1,
2183
- matrix[i - 1][j] + 1
2184
- );
2185
- }
2186
- }
2187
- }
2188
- return matrix[b.length][a.length];
2189
- }
2190
2385
 
2191
2386
  // src/commands/suggest.ts
2192
2387
  import skott3 from "skott";
@@ -2365,60 +2560,7 @@ function findTargetFile2(target, allFiles) {
2365
2560
  return null;
2366
2561
  }
2367
2562
  function formatNotFound2(target, allFiles) {
2368
- const normalizedTarget = target.toLowerCase();
2369
- const similar = allFiles.filter((f) => {
2370
- const fileName = f.split("/").pop()?.toLowerCase() || "";
2371
- const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2372
- return fileName.includes(normalizedTarget) || fileNameNoExt.includes(normalizedTarget) || levenshteinDistance2(fileNameNoExt, normalizedTarget) <= 3;
2373
- }).slice(0, 5);
2374
- let out = `Arquivo nao encontrado no indice: "${target}"
2375
-
2376
- `;
2377
- out += `Total de arquivos indexados: ${allFiles.length}
2378
-
2379
- `;
2380
- if (similar.length > 0) {
2381
- out += `Arquivos com nome similar:
2382
- `;
2383
- for (const s of similar) {
2384
- out += ` - ${s}
2385
- `;
2386
- }
2387
- out += `
2388
- `;
2389
- }
2390
- out += `Dicas:
2391
- `;
2392
- out += ` - Use o caminho relativo: src/components/Header.tsx
2393
- `;
2394
- out += ` - Ou apenas o nome do arquivo: Header
2395
- `;
2396
- out += ` - Verifique se o arquivo esta em uma pasta incluida no scan
2397
- `;
2398
- return out;
2399
- }
2400
- function levenshteinDistance2(a, b) {
2401
- const matrix = [];
2402
- for (let i = 0; i <= b.length; i++) {
2403
- matrix[i] = [i];
2404
- }
2405
- for (let j = 0; j <= a.length; j++) {
2406
- matrix[0][j] = j;
2407
- }
2408
- for (let i = 1; i <= b.length; i++) {
2409
- for (let j = 1; j <= a.length; j++) {
2410
- if (b.charAt(i - 1) === a.charAt(j - 1)) {
2411
- matrix[i][j] = matrix[i - 1][j - 1];
2412
- } else {
2413
- matrix[i][j] = Math.min(
2414
- matrix[i - 1][j - 1] + 1,
2415
- matrix[i][j - 1] + 1,
2416
- matrix[i - 1][j] + 1
2417
- );
2418
- }
2419
- }
2420
- }
2421
- return matrix[b.length][a.length];
2563
+ return formatFileNotFound({ target, allFiles, command: "suggest" });
2422
2564
  }
2423
2565
 
2424
2566
  // src/commands/context.ts
@@ -2740,61 +2882,8 @@ function shouldIgnore(name) {
2740
2882
  return ignoredDirs.includes(name) || name.startsWith(".");
2741
2883
  }
2742
2884
  function formatNotFound3(target, cwd) {
2743
- const normalizedTarget = target.toLowerCase();
2744
2885
  const allFiles = getAllCodeFiles(cwd);
2745
- const similar = allFiles.filter((f) => {
2746
- const fileName = basename(f).toLowerCase();
2747
- const fileNameNoExt = fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
2748
- return fileName.includes(normalizedTarget) || fileNameNoExt.includes(normalizedTarget) || levenshteinDistance3(fileNameNoExt, normalizedTarget) <= 3;
2749
- }).slice(0, 5);
2750
- let out = `Arquivo nao encontrado: "${target}"
2751
-
2752
- `;
2753
- out += `Total de arquivos no projeto: ${allFiles.length}
2754
-
2755
- `;
2756
- if (similar.length > 0) {
2757
- out += `Arquivos com nome similar:
2758
- `;
2759
- for (const s of similar) {
2760
- out += ` - ${s}
2761
- `;
2762
- }
2763
- out += `
2764
- `;
2765
- }
2766
- out += `Dicas:
2767
- `;
2768
- out += ` - Use o caminho relativo: src/components/Header.tsx
2769
- `;
2770
- out += ` - Ou apenas o nome do arquivo: Header
2771
- `;
2772
- out += ` - Verifique se o arquivo existe e e um arquivo .ts/.tsx/.js/.jsx
2773
- `;
2774
- return out;
2775
- }
2776
- function levenshteinDistance3(a, b) {
2777
- const matrix = [];
2778
- for (let i = 0; i <= b.length; i++) {
2779
- matrix[i] = [i];
2780
- }
2781
- for (let j = 0; j <= a.length; j++) {
2782
- matrix[0][j] = j;
2783
- }
2784
- for (let i = 1; i <= b.length; i++) {
2785
- for (let j = 1; j <= a.length; j++) {
2786
- if (b.charAt(i - 1) === a.charAt(j - 1)) {
2787
- matrix[i][j] = matrix[i - 1][j - 1];
2788
- } else {
2789
- matrix[i][j] = Math.min(
2790
- matrix[i - 1][j - 1] + 1,
2791
- matrix[i][j - 1] + 1,
2792
- matrix[i - 1][j] + 1
2793
- );
2794
- }
2795
- }
2796
- }
2797
- return matrix[b.length][a.length];
2886
+ return formatFileNotFound({ target, allFiles, command: "context" });
2798
2887
  }
2799
2888
 
2800
2889
  // src/commands/areas.ts
@@ -2963,7 +3052,7 @@ async function area(target, options = {}) {
2963
3052
  }
2964
3053
  if (areaFiles.length === 0) {
2965
3054
  const availableAreas = getAvailableAreas(filteredFiles, config);
2966
- return formatAreaNotFound(target, availableAreas);
3055
+ return formatAreaNotFound2(target, availableAreas);
2967
3056
  }
2968
3057
  const byCategory = {};
2969
3058
  const categories = {};
@@ -3030,33 +3119,8 @@ function getAvailableAreas(allFiles, config) {
3030
3119
  }
3031
3120
  return [...areaCounts.entries()].map(([id, count]) => ({ id, count })).sort((a, b) => b.count - a.count);
3032
3121
  }
3033
- function formatAreaNotFound(target, availableAreas) {
3034
- let out = `
3035
- \u274C \xC1rea n\xE3o encontrada: "${target}"
3036
-
3037
- `;
3038
- if (availableAreas.length > 0) {
3039
- out += `\u{1F4E6} \xC1reas dispon\xEDveis:
3040
-
3041
- `;
3042
- for (const { id, count } of availableAreas.slice(0, 15)) {
3043
- out += ` ${id.padEnd(25)} ${count} arquivos
3044
- `;
3045
- }
3046
- if (availableAreas.length > 15) {
3047
- out += ` ... e mais ${availableAreas.length - 15}
3048
- `;
3049
- }
3050
- out += `
3051
- `;
3052
- }
3053
- out += `\u{1F4A1} Dicas:
3054
- `;
3055
- out += ` - Use o ID exato da \xE1rea (ex: ai-tool area auth)
3056
- `;
3057
- out += ` - Use 'ai-tool areas' para listar todas as \xE1reas
3058
- `;
3059
- return out;
3122
+ function formatAreaNotFound2(target, availableAreas) {
3123
+ return formatAreaNotFound({ target, availableAreas });
3060
3124
  }
3061
3125
  function getAllCodeFiles3(dir, files = [], baseDir = dir) {
3062
3126
  try {
@@ -3278,6 +3342,15 @@ export {
3278
3342
  map,
3279
3343
  dead,
3280
3344
  deadFix,
3345
+ levenshteinDistance,
3346
+ findSimilar,
3347
+ findBestMatch,
3348
+ extractFileName,
3349
+ COMMAND_REFERENCE,
3350
+ formatFileNotFound,
3351
+ formatAreaNotFound,
3352
+ formatMissingTarget,
3353
+ formatInvalidCommand,
3281
3354
  impact,
3282
3355
  suggest,
3283
3356
  context,
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  impact,
11
11
  map,
12
12
  suggest
13
- } from "./chunk-QPC6XJKI.js";
13
+ } from "./chunk-PHTWZ2JT.js";
14
14
 
15
15
  // src/cli.ts
16
16
  var HELP = `
@@ -87,7 +87,7 @@ async function main() {
87
87
  }
88
88
  }
89
89
  if (flags.mcp) {
90
- const { startMcpServer } = await import("./server-BD4ZIFRC.js");
90
+ const { startMcpServer } = await import("./server-NIEAOPLJ.js");
91
91
  await startMcpServer();
92
92
  return;
93
93
  }
package/dist/index.d.ts CHANGED
@@ -331,6 +331,139 @@ declare function isEntryPoint(filePath: string): boolean;
331
331
  */
332
332
  declare function isCodeFile(filePath: string): boolean;
333
333
 
334
+ /**
335
+ * Funções de similaridade de strings
336
+ *
337
+ * Usa algoritmo de Levenshtein para calcular distância entre strings
338
+ * e encontrar candidatos similares para sugestões "você quis dizer?"
339
+ */
340
+ /**
341
+ * Calcula a distância de Levenshtein entre duas strings
342
+ *
343
+ * A distância representa o número mínimo de operações (inserção, deleção, substituição)
344
+ * necessárias para transformar uma string na outra.
345
+ *
346
+ * @param a - Primeira string
347
+ * @param b - Segunda string
348
+ * @returns Número de edições necessárias
349
+ *
350
+ * @example
351
+ * levenshteinDistance('auth', 'auht') // 2 (troca de posição = 2 substituições)
352
+ * levenshteinDistance('auth', 'auth') // 0 (iguais)
353
+ * levenshteinDistance('auth', 'auths') // 1 (1 inserção)
354
+ */
355
+ declare function levenshteinDistance(a: string, b: string): number;
356
+ /**
357
+ * Opções para busca de itens similares
358
+ */
359
+ interface FindSimilarOptions<T> {
360
+ /** Distância máxima de Levenshtein para considerar similar (default: 3) */
361
+ maxDistance?: number;
362
+ /** Número máximo de resultados (default: 5) */
363
+ limit?: number;
364
+ /** Normalizar para lowercase antes de comparar (default: true) */
365
+ normalize?: boolean;
366
+ /** Função para extrair a chave de comparação de cada item */
367
+ extractKey?: (item: T) => string;
368
+ }
369
+ /**
370
+ * Encontra itens similares a um target
371
+ *
372
+ * Usa Levenshtein distance e match de substring para encontrar candidatos.
373
+ * Substring match tem prioridade (score 0), seguido por Levenshtein.
374
+ *
375
+ * @param target - Termo buscado
376
+ * @param candidates - Lista de candidatos
377
+ * @param options - Configurações opcionais
378
+ * @returns Lista de candidatos similares, ordenados por relevância
379
+ *
380
+ * @example
381
+ * const areas = ['auth', 'dashboard', 'stripe'];
382
+ * findSimilar('auht', areas) // ['auth']
383
+ * findSimilar('dash', areas) // ['dashboard']
384
+ */
385
+ declare function findSimilar<T>(target: string, candidates: T[], options?: FindSimilarOptions<T>): T[];
386
+ /**
387
+ * Encontra o melhor match para sugestão "você quis dizer?"
388
+ *
389
+ * Mais restritivo que findSimilar (maxDistance: 2) para evitar
390
+ * sugestões incorretas.
391
+ *
392
+ * @param target - Termo buscado
393
+ * @param candidates - Lista de candidatos
394
+ * @param extractKey - Função para extrair chave de comparação
395
+ * @returns Melhor match ou null se não houver sugestão confiável
396
+ *
397
+ * @example
398
+ * findBestMatch('auht', ['auth', 'dashboard']) // 'auth'
399
+ * findBestMatch('xyz', ['auth', 'dashboard']) // null
400
+ */
401
+ declare function findBestMatch<T>(target: string, candidates: T[], extractKey?: (item: T) => string): T | null;
402
+ /**
403
+ * Extrai nome do arquivo de um caminho (sem extensão)
404
+ *
405
+ * Útil para comparar arquivos ignorando caminho e extensão.
406
+ *
407
+ * @param filePath - Caminho do arquivo
408
+ * @returns Nome do arquivo sem extensão
409
+ *
410
+ * @example
411
+ * extractFileName('src/hooks/useAuth.ts') // 'useAuth'
412
+ * extractFileName('Button.tsx') // 'Button'
413
+ */
414
+ declare function extractFileName(filePath: string): string;
415
+
416
+ /**
417
+ * Formatadores de mensagens de erro com sugestões inteligentes
418
+ *
419
+ * Todas as funções retornam strings formatadas para exibição,
420
+ * com sugestões "você quis dizer?" e referência de comandos.
421
+ */
422
+ /**
423
+ * Referência rápida de comandos disponíveis
424
+ */
425
+ declare const COMMAND_REFERENCE: Record<string, string>;
426
+ interface FormatFileNotFoundOptions {
427
+ /** Termo buscado */
428
+ target: string;
429
+ /** Lista de todos os arquivos disponíveis */
430
+ allFiles: string[];
431
+ /** Comando que gerou o erro (para contexto) */
432
+ command?: string;
433
+ }
434
+ /**
435
+ * Formata mensagem de arquivo não encontrado
436
+ *
437
+ * Inclui sugestões de arquivos similares e referência de comandos.
438
+ */
439
+ declare function formatFileNotFound(options: FormatFileNotFoundOptions): string;
440
+ interface AreaInfo {
441
+ /** ID da área */
442
+ id: string;
443
+ /** Número de arquivos na área */
444
+ count: number;
445
+ }
446
+ interface FormatAreaNotFoundOptions {
447
+ /** Termo buscado */
448
+ target: string;
449
+ /** Lista de áreas disponíveis */
450
+ availableAreas: AreaInfo[];
451
+ }
452
+ /**
453
+ * Formata mensagem de área não encontrada
454
+ *
455
+ * Inclui sugestões de áreas similares usando Levenshtein.
456
+ */
457
+ declare function formatAreaNotFound(options: FormatAreaNotFoundOptions): string;
458
+ /**
459
+ * Formata mensagem de target obrigatório faltando
460
+ */
461
+ declare function formatMissingTarget(command: string): string;
462
+ /**
463
+ * Formata mensagem de comando inválido
464
+ */
465
+ declare function formatInvalidCommand(command: string): string;
466
+
334
467
  /**
335
468
  * Utilitários para detecção de Firebase Cloud Functions
336
469
  *
@@ -517,4 +650,4 @@ declare const AREA_DESCRIPTIONS: Record<string, string>;
517
650
 
518
651
  declare const VERSION: string;
519
652
 
520
- export { AREA_DESCRIPTIONS, AREA_NAMES, type AreaConfig, type AreaDetailResult, type AreaFile, type AreaOptions, type AreasConfigFile, type AreasOptions, type AreasResult, type CommandOptions, type ContextOptions, type ContextResult, type DeadFile, type DeadOptions, type DeadResult, type DetectedArea, FOLDER_PATTERNS, type FileCategory, type FileInfo, type FolderStats, type FunctionInfo, type ImpactFile, type ImpactOptions, type ImpactResult, type ImportInfo, KEYWORD_PATTERNS, type MapOptions, type MapResult, type OutputFormat, type ParamInfo, type RiskInfo, type SuggestOptions, type SuggestResult, type Suggestion, type SuggestionPriority, type TypeInfo, type TypeKind, VERSION, area, areas, areasInit, categoryIcons, clearFirebaseCache, configExists, context, dead, deadFix, detectCategory, detectFileAreas, filterCloudFunctionsFalsePositives, getAreaDescription, getAreaName, getCacheDir, getFileDescription, hasFirebaseFunctions, impact, inferFileDescription, invalidateCache, isCacheValid, isCodeFile, isEntryPoint, isExportedCloudFunction, isFirebaseProject, map, readConfig, removeArea, setArea, setFileDescription, suggest, writeConfig };
653
+ export { AREA_DESCRIPTIONS, AREA_NAMES, type AreaConfig, type AreaDetailResult, type AreaFile, type AreaInfo, type AreaOptions, type AreasConfigFile, type AreasOptions, type AreasResult, COMMAND_REFERENCE, type CommandOptions, type ContextOptions, type ContextResult, type DeadFile, type DeadOptions, type DeadResult, type DetectedArea, FOLDER_PATTERNS, type FileCategory, type FileInfo, type FindSimilarOptions, type FolderStats, type FormatAreaNotFoundOptions, type FormatFileNotFoundOptions, type FunctionInfo, type ImpactFile, type ImpactOptions, type ImpactResult, type ImportInfo, KEYWORD_PATTERNS, type MapOptions, type MapResult, type OutputFormat, type ParamInfo, type RiskInfo, type SuggestOptions, type SuggestResult, type Suggestion, type SuggestionPriority, type TypeInfo, type TypeKind, VERSION, area, areas, areasInit, categoryIcons, clearFirebaseCache, configExists, context, dead, deadFix, detectCategory, detectFileAreas, extractFileName, filterCloudFunctionsFalsePositives, findBestMatch, findSimilar, formatAreaNotFound, formatFileNotFound, formatInvalidCommand, formatMissingTarget, getAreaDescription, getAreaName, getCacheDir, getFileDescription, hasFirebaseFunctions, impact, inferFileDescription, invalidateCache, isCacheValid, isCodeFile, isEntryPoint, isExportedCloudFunction, isFirebaseProject, levenshteinDistance, map, readConfig, removeArea, setArea, setFileDescription, suggest, writeConfig };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AREA_DESCRIPTIONS,
3
3
  AREA_NAMES,
4
+ COMMAND_REFERENCE,
4
5
  FOLDER_PATTERNS,
5
6
  KEYWORD_PATTERNS,
6
7
  VERSION,
@@ -15,7 +16,14 @@ import {
15
16
  deadFix,
16
17
  detectCategory,
17
18
  detectFileAreas,
19
+ extractFileName,
18
20
  filterCloudFunctionsFalsePositives,
21
+ findBestMatch,
22
+ findSimilar,
23
+ formatAreaNotFound,
24
+ formatFileNotFound,
25
+ formatInvalidCommand,
26
+ formatMissingTarget,
19
27
  getAreaDescription,
20
28
  getAreaName,
21
29
  getCacheDir,
@@ -29,6 +37,7 @@ import {
29
37
  isEntryPoint,
30
38
  isExportedCloudFunction,
31
39
  isFirebaseProject,
40
+ levenshteinDistance,
32
41
  map,
33
42
  readConfig,
34
43
  removeArea,
@@ -36,10 +45,11 @@ import {
36
45
  setFileDescription,
37
46
  suggest,
38
47
  writeConfig
39
- } from "./chunk-QPC6XJKI.js";
48
+ } from "./chunk-PHTWZ2JT.js";
40
49
  export {
41
50
  AREA_DESCRIPTIONS,
42
51
  AREA_NAMES,
52
+ COMMAND_REFERENCE,
43
53
  FOLDER_PATTERNS,
44
54
  KEYWORD_PATTERNS,
45
55
  VERSION,
@@ -54,7 +64,14 @@ export {
54
64
  deadFix,
55
65
  detectCategory,
56
66
  detectFileAreas,
67
+ extractFileName,
57
68
  filterCloudFunctionsFalsePositives,
69
+ findBestMatch,
70
+ findSimilar,
71
+ formatAreaNotFound,
72
+ formatFileNotFound,
73
+ formatInvalidCommand,
74
+ formatMissingTarget,
58
75
  getAreaDescription,
59
76
  getAreaName,
60
77
  getCacheDir,
@@ -68,6 +85,7 @@ export {
68
85
  isEntryPoint,
69
86
  isExportedCloudFunction,
70
87
  isFirebaseProject,
88
+ levenshteinDistance,
71
89
  map,
72
90
  readConfig,
73
91
  removeArea,
@@ -8,7 +8,7 @@ import {
8
8
  impact,
9
9
  map,
10
10
  suggest
11
- } from "./chunk-QPC6XJKI.js";
11
+ } from "./chunk-PHTWZ2JT.js";
12
12
 
13
13
  // src/mcp/server.ts
14
14
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justmpm/ai-tool",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "Ferramenta de análise de dependências e impacto para projetos TypeScript/JavaScript. Usa Skott + Knip internamente.",
5
5
  "keywords": [
6
6
  "dependency-analysis",