@justmpm/ai-tool 0.9.2 → 0.9.3
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.
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cacheSymbolsIndex,
|
|
3
|
+
detectFileAreas,
|
|
4
|
+
findSimilar,
|
|
5
|
+
formatOutput,
|
|
6
|
+
getAllCodeFiles,
|
|
7
|
+
getCachedSymbolsIndex,
|
|
8
|
+
indexProject,
|
|
9
|
+
isCacheValid,
|
|
10
|
+
isFileIgnored,
|
|
11
|
+
parseCommandOptions,
|
|
12
|
+
readConfig,
|
|
13
|
+
updateCacheMeta
|
|
14
|
+
} from "./chunk-J3MX6NK3.js";
|
|
15
|
+
|
|
16
|
+
// src/commands/describe.ts
|
|
17
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
18
|
+
// PT-BR
|
|
19
|
+
"de",
|
|
20
|
+
"da",
|
|
21
|
+
"do",
|
|
22
|
+
"das",
|
|
23
|
+
"dos",
|
|
24
|
+
"para",
|
|
25
|
+
"com",
|
|
26
|
+
"em",
|
|
27
|
+
"uma",
|
|
28
|
+
"um",
|
|
29
|
+
"o",
|
|
30
|
+
"a",
|
|
31
|
+
"os",
|
|
32
|
+
"as",
|
|
33
|
+
"no",
|
|
34
|
+
"na",
|
|
35
|
+
"nos",
|
|
36
|
+
"nas",
|
|
37
|
+
"pelo",
|
|
38
|
+
"pela",
|
|
39
|
+
"que",
|
|
40
|
+
"e",
|
|
41
|
+
"ou",
|
|
42
|
+
"se",
|
|
43
|
+
"ao",
|
|
44
|
+
"aos",
|
|
45
|
+
// EN
|
|
46
|
+
"the",
|
|
47
|
+
"of",
|
|
48
|
+
"in",
|
|
49
|
+
"for",
|
|
50
|
+
"with",
|
|
51
|
+
"on",
|
|
52
|
+
"at",
|
|
53
|
+
"to",
|
|
54
|
+
"and",
|
|
55
|
+
"or",
|
|
56
|
+
"is",
|
|
57
|
+
"are",
|
|
58
|
+
"was",
|
|
59
|
+
"by",
|
|
60
|
+
"an"
|
|
61
|
+
]);
|
|
62
|
+
function removeStopwords(words) {
|
|
63
|
+
const filtered = words.filter((w) => !STOPWORDS.has(w) && w.length > 1);
|
|
64
|
+
return filtered.length > 0 ? filtered : words;
|
|
65
|
+
}
|
|
66
|
+
function calculatePartialScore(queryWords, searchableText) {
|
|
67
|
+
if (queryWords.length === 0) return 1;
|
|
68
|
+
const fullQuery = queryWords.join(" ");
|
|
69
|
+
if (searchableText.includes(fullQuery)) return 0;
|
|
70
|
+
let found = 0;
|
|
71
|
+
for (const word of queryWords) {
|
|
72
|
+
if (searchableText.includes(word)) {
|
|
73
|
+
found++;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return 1 - found / queryWords.length;
|
|
77
|
+
}
|
|
78
|
+
function buildAreaFileMap(allFiles, config) {
|
|
79
|
+
const map = /* @__PURE__ */ new Map();
|
|
80
|
+
for (const filePath of allFiles) {
|
|
81
|
+
if (isFileIgnored(filePath, config)) continue;
|
|
82
|
+
const fileAreas = detectFileAreas(filePath, config);
|
|
83
|
+
for (const areaId of fileAreas) {
|
|
84
|
+
if (!map.has(areaId)) map.set(areaId, []);
|
|
85
|
+
map.get(areaId).push(filePath);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return map;
|
|
89
|
+
}
|
|
90
|
+
function buildSearchableText(candidate, config, areaFileMap, index) {
|
|
91
|
+
const metadata = `${candidate.id} ${candidate.name} ${candidate.description}`;
|
|
92
|
+
const keywords = config.areas[candidate.id]?.keywords?.join(" ") ?? "";
|
|
93
|
+
const areaFiles = areaFileMap.get(candidate.id) ?? [];
|
|
94
|
+
const fileNames = areaFiles.map((f) => {
|
|
95
|
+
const name = f.split("/").pop() ?? "";
|
|
96
|
+
return name.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
97
|
+
}).join(" ");
|
|
98
|
+
const symbolNames = [];
|
|
99
|
+
for (const filePath of areaFiles) {
|
|
100
|
+
const fileData = index.files[filePath];
|
|
101
|
+
if (fileData?.symbols) {
|
|
102
|
+
for (const symbol of fileData.symbols) {
|
|
103
|
+
if (symbol.isExported) {
|
|
104
|
+
symbolNames.push(symbol.name);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return `${metadata} ${keywords} ${fileNames} ${symbolNames.join(" ")}`.toLowerCase();
|
|
110
|
+
}
|
|
111
|
+
function findAreaMatches(normalizedQuery, candidates, config, cwd) {
|
|
112
|
+
const allFiles = getAllCodeFiles(cwd);
|
|
113
|
+
let index;
|
|
114
|
+
if (isCacheValid(cwd)) {
|
|
115
|
+
const cached = getCachedSymbolsIndex(cwd);
|
|
116
|
+
if (cached?.files) {
|
|
117
|
+
index = cached;
|
|
118
|
+
} else {
|
|
119
|
+
index = indexProject(cwd);
|
|
120
|
+
cacheSymbolsIndex(cwd, index);
|
|
121
|
+
updateCacheMeta(cwd);
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
index = indexProject(cwd);
|
|
125
|
+
cacheSymbolsIndex(cwd, index);
|
|
126
|
+
updateCacheMeta(cwd);
|
|
127
|
+
}
|
|
128
|
+
const areaFileMap = buildAreaFileMap(allFiles, config);
|
|
129
|
+
const queryWords = removeStopwords(normalizedQuery.split(/\s+/));
|
|
130
|
+
const matches = [];
|
|
131
|
+
for (const candidate of candidates) {
|
|
132
|
+
const searchableText = buildSearchableText(candidate, config, areaFileMap, index);
|
|
133
|
+
const score = calculatePartialScore(queryWords, searchableText);
|
|
134
|
+
if (score < 0.6) {
|
|
135
|
+
const areaFiles = areaFileMap.get(candidate.id) ?? [];
|
|
136
|
+
matches.push({
|
|
137
|
+
id: candidate.id,
|
|
138
|
+
name: candidate.name,
|
|
139
|
+
description: candidate.description || "Sem descricao",
|
|
140
|
+
files: areaFiles,
|
|
141
|
+
fileCount: areaFiles.length,
|
|
142
|
+
score
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return matches.sort((a, b) => a.score - b.score);
|
|
147
|
+
}
|
|
148
|
+
async function describe(query, options = {}) {
|
|
149
|
+
const { cwd, format } = parseCommandOptions(options);
|
|
150
|
+
if (!query || query.trim().length === 0) {
|
|
151
|
+
throw new Error("Query \xE9 obrigat\xF3ria. Exemplo: ai-tool describe 'autentica\xE7\xE3o'");
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
const config = readConfig(cwd);
|
|
155
|
+
const normalizedQuery = query.toLowerCase().trim();
|
|
156
|
+
const candidates = Object.entries(config.areas).map(([id, area]) => ({
|
|
157
|
+
id,
|
|
158
|
+
name: area.name,
|
|
159
|
+
description: area.description || ""
|
|
160
|
+
}));
|
|
161
|
+
const matches = findAreaMatches(normalizedQuery, candidates, config, cwd);
|
|
162
|
+
const suggestions = [];
|
|
163
|
+
if (matches.length === 0) {
|
|
164
|
+
const similarAreaIds = findSimilar(
|
|
165
|
+
normalizedQuery,
|
|
166
|
+
candidates.map((c) => c.id),
|
|
167
|
+
{ maxDistance: 2, limit: 3 }
|
|
168
|
+
);
|
|
169
|
+
const similarNames = findSimilar(
|
|
170
|
+
normalizedQuery,
|
|
171
|
+
candidates.map((c) => c.name),
|
|
172
|
+
{ maxDistance: 2, limit: 3 }
|
|
173
|
+
);
|
|
174
|
+
suggestions.push(
|
|
175
|
+
...similarAreaIds.map((id) => `\u2192 ai-tool describe ${id}`),
|
|
176
|
+
...similarNames.map((name) => `\u2192 ai-tool describe "${name}"`)
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const result = {
|
|
180
|
+
version: "1.0.0",
|
|
181
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
182
|
+
query,
|
|
183
|
+
areas: matches,
|
|
184
|
+
suggestions: suggestions.length > 0 ? suggestions : void 0
|
|
185
|
+
};
|
|
186
|
+
return formatOutput(result, format, formatDescribeText);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
189
|
+
throw new Error(`Erro ao executar describe: ${message}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function formatDescribeText(result) {
|
|
193
|
+
let out = "";
|
|
194
|
+
if (result.areas.length === 0) {
|
|
195
|
+
out += `\u274C Nenhuma \xE1rea encontrada para: "${result.query}"
|
|
196
|
+
|
|
197
|
+
`;
|
|
198
|
+
if (result.suggestions && result.suggestions.length > 0) {
|
|
199
|
+
out += `\u{1F4A1} Voc\xEA quis dizer?
|
|
200
|
+
`;
|
|
201
|
+
for (const suggestion of result.suggestions) {
|
|
202
|
+
out += ` ${suggestion}
|
|
203
|
+
`;
|
|
204
|
+
}
|
|
205
|
+
out += `
|
|
206
|
+
`;
|
|
207
|
+
}
|
|
208
|
+
out += `\u{1F4D6} Dica: Use 'ai-tool areas' para listar todas as \xE1reas dispon\xEDveis`;
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
211
|
+
out += `\u{1F50D} Busca: "${result.query}"
|
|
212
|
+
|
|
213
|
+
`;
|
|
214
|
+
for (const area of result.areas) {
|
|
215
|
+
out += `## ${area.name} (${area.id})
|
|
216
|
+
`;
|
|
217
|
+
out += `${area.description}
|
|
218
|
+
`;
|
|
219
|
+
out += `\u{1F4C1} ${area.fileCount} arquivo(s)
|
|
220
|
+
|
|
221
|
+
`;
|
|
222
|
+
if (area.files.length > 0) {
|
|
223
|
+
out += `Arquivos:
|
|
224
|
+
`;
|
|
225
|
+
for (const file of area.files) {
|
|
226
|
+
out += ` \u2022 ${file}
|
|
227
|
+
`;
|
|
228
|
+
}
|
|
229
|
+
out += "\n";
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
out += `\u{1F4D6} Pr\xF3ximos passos:
|
|
233
|
+
`;
|
|
234
|
+
out += ` \u2192 ai-tool area <id> - ver detalhes de uma \xE1rea
|
|
235
|
+
`;
|
|
236
|
+
out += ` \u2192 ai-tool context --area=<id> - contexto completo de uma \xE1rea
|
|
237
|
+
`;
|
|
238
|
+
return out;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export {
|
|
242
|
+
describe
|
|
243
|
+
};
|
|
@@ -5125,7 +5125,10 @@ export {
|
|
|
5125
5125
|
clearFirebaseCache,
|
|
5126
5126
|
getCacheDir,
|
|
5127
5127
|
isCacheValid,
|
|
5128
|
+
updateCacheMeta,
|
|
5128
5129
|
invalidateCache,
|
|
5130
|
+
cacheSymbolsIndex,
|
|
5131
|
+
getCachedSymbolsIndex,
|
|
5129
5132
|
configExists,
|
|
5130
5133
|
readConfig,
|
|
5131
5134
|
writeConfig,
|
|
@@ -5154,6 +5157,8 @@ export {
|
|
|
5154
5157
|
formatInvalidCommand,
|
|
5155
5158
|
impact,
|
|
5156
5159
|
suggest,
|
|
5160
|
+
getAllCodeFiles,
|
|
5161
|
+
indexProject,
|
|
5157
5162
|
context,
|
|
5158
5163
|
areaContext,
|
|
5159
5164
|
areas,
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
describe
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5RMB6OEA.js";
|
|
5
5
|
import {
|
|
6
6
|
VERSION,
|
|
7
7
|
area,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
impact,
|
|
17
17
|
map,
|
|
18
18
|
suggest
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-J3MX6NK3.js";
|
|
20
20
|
|
|
21
21
|
// src/cli.ts
|
|
22
22
|
import { resolve } from "path";
|
|
@@ -113,7 +113,7 @@ async function main() {
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
if (flags.mcp) {
|
|
116
|
-
const { startMcpServer } = await import("./server-
|
|
116
|
+
const { startMcpServer } = await import("./server-TDSMX4PG.js");
|
|
117
117
|
await startMcpServer();
|
|
118
118
|
return;
|
|
119
119
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
describe
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-5RMB6OEA.js";
|
|
4
4
|
import {
|
|
5
5
|
VERSION,
|
|
6
6
|
area,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
impact,
|
|
15
15
|
map,
|
|
16
16
|
suggest
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-J3MX6NK3.js";
|
|
18
18
|
|
|
19
19
|
// src/mcp/server.ts
|
|
20
20
|
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.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "Ferramenta de análise de dependências e impacto para projetos TypeScript/JavaScript. Usa Skott + Knip internamente. Inclui busca por descrição, integração Git e testes inteligentes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dependency-analysis",
|
package/dist/chunk-2ONTJF3R.js
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
findSimilar,
|
|
3
|
-
formatOutput,
|
|
4
|
-
parseCommandOptions,
|
|
5
|
-
readConfig
|
|
6
|
-
} from "./chunk-6ZWPDZDS.js";
|
|
7
|
-
|
|
8
|
-
// src/commands/describe.ts
|
|
9
|
-
async function describe(query, options = {}) {
|
|
10
|
-
const { cwd, format } = parseCommandOptions(options);
|
|
11
|
-
if (!query || query.trim().length === 0) {
|
|
12
|
-
throw new Error("Query \xE9 obrigat\xF3ria. Exemplo: ai-tool describe 'autentica\xE7\xE3o'");
|
|
13
|
-
}
|
|
14
|
-
try {
|
|
15
|
-
const config = readConfig(cwd);
|
|
16
|
-
const normalizedQuery = query.toLowerCase().trim();
|
|
17
|
-
const candidates = Object.entries(config.areas).map(([id, area]) => ({
|
|
18
|
-
id,
|
|
19
|
-
name: area.name,
|
|
20
|
-
description: area.description || ""
|
|
21
|
-
}));
|
|
22
|
-
const matches = findAreaMatches(normalizedQuery, candidates, config);
|
|
23
|
-
const suggestions = [];
|
|
24
|
-
if (matches.length === 0) {
|
|
25
|
-
const similarAreaIds = findSimilar(
|
|
26
|
-
normalizedQuery,
|
|
27
|
-
candidates.map((c) => c.id),
|
|
28
|
-
{ maxDistance: 2, limit: 3 }
|
|
29
|
-
);
|
|
30
|
-
const similarNames = findSimilar(
|
|
31
|
-
normalizedQuery,
|
|
32
|
-
candidates.map((c) => c.name),
|
|
33
|
-
{ maxDistance: 2, limit: 3 }
|
|
34
|
-
);
|
|
35
|
-
suggestions.push(
|
|
36
|
-
...similarAreaIds.map((id) => `\u2192 ai-tool describe ${id}`),
|
|
37
|
-
...similarNames.map((name) => `\u2192 ai-tool describe "${name}"`)
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
const result = {
|
|
41
|
-
version: "1.0.0",
|
|
42
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
43
|
-
query,
|
|
44
|
-
areas: matches,
|
|
45
|
-
suggestions: suggestions.length > 0 ? suggestions : void 0
|
|
46
|
-
};
|
|
47
|
-
return formatOutput(result, format, formatDescribeText);
|
|
48
|
-
} catch (error) {
|
|
49
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
50
|
-
throw new Error(`Erro ao executar describe: ${message}`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
function findAreaMatches(normalizedQuery, candidates, config) {
|
|
54
|
-
const matches = [];
|
|
55
|
-
for (const candidate of candidates) {
|
|
56
|
-
const searchableText = `${candidate.id} ${candidate.name} ${candidate.description}`.toLowerCase();
|
|
57
|
-
const hasDirectMatch = searchableText.includes(normalizedQuery);
|
|
58
|
-
const queryWords = normalizedQuery.split(/\s+/);
|
|
59
|
-
const allWordsMatch = queryWords.every((word) => searchableText.includes(word));
|
|
60
|
-
if (hasDirectMatch || allWordsMatch) {
|
|
61
|
-
const areaFiles = getAreaFiles(candidate.id, config);
|
|
62
|
-
const score = calculateRelevanceScore(normalizedQuery, searchableText);
|
|
63
|
-
matches.push({
|
|
64
|
-
id: candidate.id,
|
|
65
|
-
name: candidate.name,
|
|
66
|
-
description: candidate.description || "Sem descri\xE7\xE3o",
|
|
67
|
-
files: areaFiles,
|
|
68
|
-
fileCount: areaFiles.length,
|
|
69
|
-
score
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return matches.sort((a, b) => a.score - b.score);
|
|
74
|
-
}
|
|
75
|
-
function getAreaFiles(areaId, config) {
|
|
76
|
-
const files = [];
|
|
77
|
-
const areaConfig = config.areas[areaId];
|
|
78
|
-
if (areaConfig?.patterns) {
|
|
79
|
-
files.push(`[Use 'ai-tool area ${areaId}' para ver arquivos completos]`);
|
|
80
|
-
}
|
|
81
|
-
return files;
|
|
82
|
-
}
|
|
83
|
-
function calculateRelevanceScore(query, text) {
|
|
84
|
-
if (text.includes(query)) {
|
|
85
|
-
return 0;
|
|
86
|
-
}
|
|
87
|
-
const queryWords = query.split(/\s+/).filter(Boolean);
|
|
88
|
-
const allWordsPresent = queryWords.every((word) => text.includes(word));
|
|
89
|
-
if (allWordsPresent) {
|
|
90
|
-
return 1;
|
|
91
|
-
}
|
|
92
|
-
return 10;
|
|
93
|
-
}
|
|
94
|
-
function formatDescribeText(result) {
|
|
95
|
-
let out = "";
|
|
96
|
-
if (result.areas.length === 0) {
|
|
97
|
-
out += `\u274C Nenhuma \xE1rea encontrada para: "${result.query}"
|
|
98
|
-
|
|
99
|
-
`;
|
|
100
|
-
if (result.suggestions && result.suggestions.length > 0) {
|
|
101
|
-
out += `\u{1F4A1} Voc\xEA quis dizer?
|
|
102
|
-
`;
|
|
103
|
-
for (const suggestion of result.suggestions) {
|
|
104
|
-
out += ` ${suggestion}
|
|
105
|
-
`;
|
|
106
|
-
}
|
|
107
|
-
out += `
|
|
108
|
-
`;
|
|
109
|
-
}
|
|
110
|
-
out += `\u{1F4D6} Dica: Use 'ai-tool areas' para listar todas as \xE1reas dispon\xEDveis`;
|
|
111
|
-
return out;
|
|
112
|
-
}
|
|
113
|
-
out += `\u{1F50D} Busca: "${result.query}"
|
|
114
|
-
|
|
115
|
-
`;
|
|
116
|
-
for (const area of result.areas) {
|
|
117
|
-
out += `## ${area.name} (${area.id})
|
|
118
|
-
`;
|
|
119
|
-
out += `${area.description}
|
|
120
|
-
`;
|
|
121
|
-
out += `\u{1F4C1} ${area.fileCount} arquivo(s)
|
|
122
|
-
|
|
123
|
-
`;
|
|
124
|
-
if (area.files.length > 0) {
|
|
125
|
-
out += `Arquivos:
|
|
126
|
-
`;
|
|
127
|
-
for (const file of area.files) {
|
|
128
|
-
out += ` \u2022 ${file}
|
|
129
|
-
`;
|
|
130
|
-
}
|
|
131
|
-
out += "\n";
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
out += `\u{1F4D6} Pr\xF3ximos passos:
|
|
135
|
-
`;
|
|
136
|
-
out += ` \u2192 ai-tool area <id> - ver detalhes de uma \xE1rea
|
|
137
|
-
`;
|
|
138
|
-
out += ` \u2192 ai-tool context --area=<id> - contexto completo de uma \xE1rea
|
|
139
|
-
`;
|
|
140
|
-
return out;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export {
|
|
144
|
-
describe
|
|
145
|
-
};
|