@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.
- package/dist/{chunk-QPC6XJKI.js → chunk-PHTWZ2JT.js} +263 -190
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +134 -1
- package/dist/index.js +19 -1
- package/dist/{server-BD4ZIFRC.js → server-NIEAOPLJ.js} +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
3034
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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,
|
package/package.json
CHANGED