@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.
- package/dist/{chunk-QPC6XJKI.js → chunk-XVNZLNUT.js} +279 -191
- 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-5ZBF3F6D.js} +1 -1
- package/package.json +1 -1
|
@@ -860,7 +860,14 @@ function formatAreaDetailText(result, options = {}) {
|
|
|
860
860
|
}
|
|
861
861
|
|
|
862
862
|
// src/cache/index.ts
|
|
863
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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;
|
|
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-
|
|
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-
|
|
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-
|
|
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,
|
package/package.json
CHANGED