@justmpm/ai-tool 0.5.2 → 0.5.5

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.
@@ -860,7 +860,14 @@ function formatAreaDetailText(result, options = {}) {
860
860
  }
861
861
 
862
862
  // src/cache/index.ts
863
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync, statSync, readdirSync } from "fs";
863
+ import {
864
+ existsSync as existsSync2,
865
+ mkdirSync,
866
+ readFileSync as readFileSync2,
867
+ writeFileSync,
868
+ statSync,
869
+ readdirSync
870
+ } from "fs";
864
871
  import { join as join2, extname } from "path";
865
872
 
866
873
  // src/utils/firebase.ts
@@ -985,6 +992,14 @@ function calculateFilesHash(cwd) {
985
992
  }
986
993
  }
987
994
  scanDir(cwd);
995
+ try {
996
+ const configPath = join2(cwd, CACHE_DIR, "areas.config.json");
997
+ if (existsSync2(configPath)) {
998
+ const stat = statSync(configPath);
999
+ timestamps.push(stat.mtimeMs);
1000
+ }
1001
+ } catch {
1002
+ }
988
1003
  const sum = timestamps.reduce((a, b) => a + b, 0);
989
1004
  return `${timestamps.length}-${Math.floor(sum)}`;
990
1005
  }
@@ -1873,6 +1888,254 @@ ${output}`;
1873
1888
 
1874
1889
  // src/commands/impact.ts
1875
1890
  import skott2 from "skott";
1891
+
1892
+ // src/utils/similarity.ts
1893
+ function levenshteinDistance(a, b) {
1894
+ const matrix = [];
1895
+ for (let i = 0; i <= b.length; i++) {
1896
+ matrix[i] = [i];
1897
+ }
1898
+ for (let j = 0; j <= a.length; j++) {
1899
+ matrix[0][j] = j;
1900
+ }
1901
+ for (let i = 1; i <= b.length; i++) {
1902
+ for (let j = 1; j <= a.length; j++) {
1903
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
1904
+ matrix[i][j] = matrix[i - 1][j - 1];
1905
+ } else {
1906
+ matrix[i][j] = Math.min(
1907
+ matrix[i - 1][j - 1] + 1,
1908
+ // substituição
1909
+ matrix[i][j - 1] + 1,
1910
+ // inserção
1911
+ matrix[i - 1][j] + 1
1912
+ // deleção
1913
+ );
1914
+ }
1915
+ }
1916
+ }
1917
+ return matrix[b.length][a.length];
1918
+ }
1919
+ function findSimilar(target, candidates, options = {}) {
1920
+ const {
1921
+ maxDistance = 3,
1922
+ limit = 5,
1923
+ normalize: normalize2 = true,
1924
+ extractKey = (item) => String(item)
1925
+ } = options;
1926
+ const normalizedTarget = normalize2 ? target.toLowerCase() : target;
1927
+ const scored = candidates.map((item) => {
1928
+ const key = extractKey(item);
1929
+ const normalizedKey = normalize2 ? key.toLowerCase() : key;
1930
+ const keyNoExt = normalizedKey.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
1931
+ let score = Infinity;
1932
+ if (normalizedKey.includes(normalizedTarget) || keyNoExt.includes(normalizedTarget)) {
1933
+ score = 0;
1934
+ } else {
1935
+ score = levenshteinDistance(keyNoExt, normalizedTarget);
1936
+ }
1937
+ return { item, score };
1938
+ }).filter(({ score }) => score <= maxDistance).sort((a, b) => a.score - b.score).slice(0, limit);
1939
+ return scored.map(({ item }) => item);
1940
+ }
1941
+ function findBestMatch(target, candidates, extractKey = (item) => String(item)) {
1942
+ const similar = findSimilar(target, candidates, {
1943
+ maxDistance: 2,
1944
+ // Mais restritivo para "você quis dizer"
1945
+ limit: 1,
1946
+ extractKey
1947
+ });
1948
+ return similar.length > 0 ? similar[0] : null;
1949
+ }
1950
+ function extractFileName(filePath) {
1951
+ const fileName = filePath.split("/").pop() || filePath;
1952
+ return fileName.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
1953
+ }
1954
+
1955
+ // src/utils/errors.ts
1956
+ var COMMAND_REFERENCE = {
1957
+ map: "Resumo do projeto (sem target)",
1958
+ areas: "Listar todas as \xE1reas (sem target)",
1959
+ area: "Arquivos de uma \xE1rea espec\xEDfica",
1960
+ suggest: "O que ler antes de editar",
1961
+ context: "API/assinaturas de um arquivo",
1962
+ impact: "Quem usa este arquivo",
1963
+ dead: "C\xF3digo morto (sem target)"
1964
+ };
1965
+ function getCommandReferenceSection(excludeCommand) {
1966
+ let out = `
1967
+ \u{1F4CC} Comandos \xFAteis:
1968
+ `;
1969
+ for (const [cmd, desc] of Object.entries(COMMAND_REFERENCE)) {
1970
+ if (cmd !== excludeCommand) {
1971
+ out += ` ai-tool ${cmd.padEnd(10)} ${desc}
1972
+ `;
1973
+ }
1974
+ }
1975
+ return out;
1976
+ }
1977
+ function formatFileNotFound(options) {
1978
+ const { target, allFiles, command } = options;
1979
+ const similarFiles = findSimilar(target, allFiles, {
1980
+ maxDistance: 3,
1981
+ limit: 5,
1982
+ extractKey: extractFileName
1983
+ });
1984
+ const bestMatch = findBestMatch(target, allFiles, extractFileName);
1985
+ let out = `
1986
+ \u274C Arquivo n\xE3o encontrado: "${target}"
1987
+
1988
+ `;
1989
+ out += `\u{1F4CA} Total de arquivos indexados: ${allFiles.length}
1990
+
1991
+ `;
1992
+ if (bestMatch) {
1993
+ out += `\u{1F4A1} Voc\xEA quis dizer?
1994
+ `;
1995
+ out += ` \u2192 ${bestMatch}
1996
+
1997
+ `;
1998
+ }
1999
+ if (similarFiles.length > 1) {
2000
+ out += `\u{1F4DD} Arquivos similares:
2001
+ `;
2002
+ for (const f of similarFiles) {
2003
+ if (f !== bestMatch) {
2004
+ out += ` \u2022 ${f}
2005
+ `;
2006
+ }
2007
+ }
2008
+ out += "\n";
2009
+ }
2010
+ out += `\u{1F4D6} Dicas:
2011
+ `;
2012
+ out += ` \u2022 Use o caminho relativo: src/components/Header.tsx
2013
+ `;
2014
+ out += ` \u2022 Ou apenas o nome do arquivo: Header
2015
+ `;
2016
+ out += ` \u2022 Verifique se o arquivo est\xE1 em uma pasta inclu\xEDda no scan
2017
+ `;
2018
+ if (command) {
2019
+ out += getCommandReferenceSection(command);
2020
+ }
2021
+ return out;
2022
+ }
2023
+ function formatAreaNotFound(options) {
2024
+ const { target, availableAreas } = options;
2025
+ const areaIds = availableAreas.map((a) => a.id);
2026
+ const bestMatchId = findBestMatch(target, areaIds);
2027
+ const similarAreaIds = findSimilar(target, areaIds, {
2028
+ maxDistance: 3,
2029
+ limit: 5
2030
+ });
2031
+ let out = `
2032
+ \u274C \xC1rea n\xE3o encontrada: "${target}"
2033
+
2034
+ `;
2035
+ if (bestMatchId) {
2036
+ out += `\u{1F4A1} Voc\xEA quis dizer?
2037
+ `;
2038
+ out += ` \u2192 ai-tool area ${bestMatchId}
2039
+
2040
+ `;
2041
+ }
2042
+ if (availableAreas.length > 0) {
2043
+ out += `\u{1F4E6} \xC1reas dispon\xEDveis:
2044
+
2045
+ `;
2046
+ if (similarAreaIds.length > 0 && !bestMatchId) {
2047
+ for (const id of similarAreaIds) {
2048
+ const area2 = availableAreas.find((a) => a.id === id);
2049
+ if (area2) {
2050
+ out += ` ${area2.id.padEnd(25)} ${area2.count} arquivos
2051
+ `;
2052
+ }
2053
+ }
2054
+ out += ` ---
2055
+ `;
2056
+ }
2057
+ const areasToShow = similarAreaIds.length > 0 && !bestMatchId ? availableAreas.filter((a) => !similarAreaIds.includes(a.id)).slice(0, 10) : availableAreas.slice(0, 15);
2058
+ for (const { id, count } of areasToShow) {
2059
+ out += ` ${id.padEnd(25)} ${count} arquivos
2060
+ `;
2061
+ }
2062
+ const totalShown = similarAreaIds.length > 0 && !bestMatchId ? similarAreaIds.length + areasToShow.length : areasToShow.length;
2063
+ if (availableAreas.length > totalShown) {
2064
+ out += ` ... e mais ${availableAreas.length - totalShown}
2065
+ `;
2066
+ }
2067
+ out += `
2068
+ `;
2069
+ }
2070
+ out += `\u{1F4D6} Dicas:
2071
+ `;
2072
+ out += ` \u2022 Use o ID exato da \xE1rea (ex: ai-tool area auth)
2073
+ `;
2074
+ out += ` \u2022 Use 'ai-tool areas' para listar todas as \xE1reas
2075
+ `;
2076
+ out += ` \u2022 IDs s\xE3o case-sensitive (Auth \u2260 auth)
2077
+ `;
2078
+ out += `
2079
+ \u{1F4CC} Comandos relacionados:
2080
+ `;
2081
+ out += ` ai-tool areas Listar todas as \xE1reas
2082
+ `;
2083
+ out += ` ai-tool map Ver estrutura do projeto
2084
+ `;
2085
+ return out;
2086
+ }
2087
+ function formatMissingTarget(command) {
2088
+ let out = `
2089
+ \u274C Erro: par\xE2metro "target" \xE9 OBRIGAT\xD3RIO para o comando "${command}".
2090
+
2091
+ `;
2092
+ out += `\u{1F4DD} Exemplos:
2093
+ `;
2094
+ if (command === "area") {
2095
+ out += ` ai-tool area auth
2096
+ `;
2097
+ out += ` ai-tool area dashboard
2098
+ `;
2099
+ out += ` ai-tool area billing --type=hook
2100
+
2101
+ `;
2102
+ out += `\u{1F4A1} Use 'ai-tool areas' para listar todas as \xE1reas dispon\xEDveis.
2103
+ `;
2104
+ } else {
2105
+ out += ` ai-tool ${command} useAuth
2106
+ `;
2107
+ out += ` ai-tool ${command} Button.tsx
2108
+ `;
2109
+ out += ` ai-tool ${command} src/hooks/useAuth.ts
2110
+ `;
2111
+ }
2112
+ out += getCommandReferenceSection(command);
2113
+ return out;
2114
+ }
2115
+ function formatInvalidCommand(command) {
2116
+ const validCommands = Object.keys(COMMAND_REFERENCE);
2117
+ const bestMatch = findBestMatch(command, validCommands);
2118
+ let out = `
2119
+ \u274C Comando inv\xE1lido: "${command}"
2120
+
2121
+ `;
2122
+ if (bestMatch) {
2123
+ out += `\u{1F4A1} Voc\xEA quis dizer?
2124
+ `;
2125
+ out += ` \u2192 ai-tool ${bestMatch}
2126
+
2127
+ `;
2128
+ }
2129
+ out += `\u{1F4CC} Comandos dispon\xEDveis:
2130
+ `;
2131
+ for (const [cmd, desc] of Object.entries(COMMAND_REFERENCE)) {
2132
+ out += ` ai-tool ${cmd.padEnd(10)} ${desc}
2133
+ `;
2134
+ }
2135
+ return out;
2136
+ }
2137
+
2138
+ // src/commands/impact.ts
1876
2139
  async function impact(target, options = {}) {
1877
2140
  const cwd = options.cwd || process.cwd();
1878
2141
  const format = options.format || "text";
@@ -2065,37 +2328,7 @@ function findTargetFile(target, allFiles) {
2065
2328
  return null;
2066
2329
  }
2067
2330
  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;
2331
+ return formatFileNotFound({ target, allFiles, command: "impact" });
2099
2332
  }
2100
2333
  function toImpactFile(isDirect) {
2101
2334
  return (path) => ({
@@ -2164,29 +2397,6 @@ function generateSuggestions(upstream, downstream, risks) {
2164
2397
  }
2165
2398
  return suggestions;
2166
2399
  }
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
2400
 
2191
2401
  // src/commands/suggest.ts
2192
2402
  import skott3 from "skott";
@@ -2365,60 +2575,7 @@ function findTargetFile2(target, allFiles) {
2365
2575
  return null;
2366
2576
  }
2367
2577
  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];
2578
+ return formatFileNotFound({ target, allFiles, command: "suggest" });
2422
2579
  }
2423
2580
 
2424
2581
  // src/commands/context.ts
@@ -2740,61 +2897,8 @@ function shouldIgnore(name) {
2740
2897
  return ignoredDirs.includes(name) || name.startsWith(".");
2741
2898
  }
2742
2899
  function formatNotFound3(target, cwd) {
2743
- const normalizedTarget = target.toLowerCase();
2744
2900
  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];
2901
+ return formatFileNotFound({ target, allFiles, command: "context" });
2798
2902
  }
2799
2903
 
2800
2904
  // src/commands/areas.ts
@@ -2963,7 +3067,7 @@ async function area(target, options = {}) {
2963
3067
  }
2964
3068
  if (areaFiles.length === 0) {
2965
3069
  const availableAreas = getAvailableAreas(filteredFiles, config);
2966
- return formatAreaNotFound(target, availableAreas);
3070
+ return formatAreaNotFound2(target, availableAreas);
2967
3071
  }
2968
3072
  const byCategory = {};
2969
3073
  const categories = {};
@@ -3030,33 +3134,8 @@ function getAvailableAreas(allFiles, config) {
3030
3134
  }
3031
3135
  return [...areaCounts.entries()].map(([id, count]) => ({ id, count })).sort((a, b) => b.count - a.count);
3032
3136
  }
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;
3137
+ function formatAreaNotFound2(target, availableAreas) {
3138
+ return formatAreaNotFound({ target, availableAreas });
3060
3139
  }
3061
3140
  function getAllCodeFiles3(dir, files = [], baseDir = dir) {
3062
3141
  try {
@@ -3278,6 +3357,15 @@ export {
3278
3357
  map,
3279
3358
  dead,
3280
3359
  deadFix,
3360
+ levenshteinDistance,
3361
+ findSimilar,
3362
+ findBestMatch,
3363
+ extractFileName,
3364
+ COMMAND_REFERENCE,
3365
+ formatFileNotFound,
3366
+ formatAreaNotFound,
3367
+ formatMissingTarget,
3368
+ formatInvalidCommand,
3281
3369
  impact,
3282
3370
  suggest,
3283
3371
  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-XVNZLNUT.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-5ZBF3F6D.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-XVNZLNUT.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-XVNZLNUT.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.5",
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",