@justmpm/ai-tool 0.9.0 → 0.9.2
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,145 @@
|
|
|
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
|
+
};
|
|
@@ -3486,20 +3486,21 @@ function extractTriggerInfo(init, triggerName) {
|
|
|
3486
3486
|
|
|
3487
3487
|
// src/ts/cache.ts
|
|
3488
3488
|
function indexProject(cwd) {
|
|
3489
|
-
const
|
|
3490
|
-
|
|
3489
|
+
const resolvedCwd = cwd || process.cwd();
|
|
3490
|
+
const allFiles = getAllCodeFiles(resolvedCwd);
|
|
3491
|
+
logger.debug(`Indexando ${allFiles.length} arquivos em ${resolvedCwd}`);
|
|
3491
3492
|
const functionFiles = allFiles.filter((f) => f.includes("functions/src/"));
|
|
3492
3493
|
if (functionFiles.length > 0) {
|
|
3493
3494
|
logger.debug(`Encontrados ${functionFiles.length} arquivos em functions/src/:`, functionFiles);
|
|
3494
3495
|
logger.debugFunctions(`Arquivos em functions/src/:`);
|
|
3495
3496
|
functionFiles.forEach((f) => logger.debugFunctions(` - ${f}`));
|
|
3496
3497
|
}
|
|
3497
|
-
const project = createProject2(
|
|
3498
|
+
const project = createProject2(resolvedCwd);
|
|
3498
3499
|
let addedCount = 0;
|
|
3499
3500
|
let errorCount = 0;
|
|
3500
3501
|
for (const file of allFiles) {
|
|
3501
3502
|
try {
|
|
3502
|
-
project.addSourceFileAtPath(resolve(
|
|
3503
|
+
project.addSourceFileAtPath(resolve(resolvedCwd, file));
|
|
3503
3504
|
addedCount++;
|
|
3504
3505
|
} catch {
|
|
3505
3506
|
errorCount++;
|
|
@@ -3524,7 +3525,7 @@ function indexProject(cwd) {
|
|
|
3524
3525
|
let symbolCount = 0;
|
|
3525
3526
|
for (const sourceFile of project.getSourceFiles()) {
|
|
3526
3527
|
let filePath = sourceFile.getFilePath().replace(/\\/g, "/");
|
|
3527
|
-
const cwdNormalized =
|
|
3528
|
+
const cwdNormalized = resolvedCwd.replace(/\\/g, "/");
|
|
3528
3529
|
if (filePath.startsWith(cwdNormalized + "/")) {
|
|
3529
3530
|
filePath = filePath.slice(cwdNormalized.length + 1);
|
|
3530
3531
|
} else if (filePath.startsWith(cwdNormalized)) {
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
describe
|
|
4
|
+
} from "./chunk-2ONTJF3R.js";
|
|
2
5
|
import {
|
|
3
6
|
VERSION,
|
|
4
7
|
area,
|
|
@@ -13,7 +16,7 @@ import {
|
|
|
13
16
|
impact,
|
|
14
17
|
map,
|
|
15
18
|
suggest
|
|
16
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-6ZWPDZDS.js";
|
|
17
20
|
|
|
18
21
|
// src/cli.ts
|
|
19
22
|
import { resolve } from "path";
|
|
@@ -30,6 +33,7 @@ COMANDOS:
|
|
|
30
33
|
context <arquivo> Extrai assinaturas de um arquivo (funcoes, tipos)
|
|
31
34
|
context --area=<nome> Contexto consolidado de toda uma area
|
|
32
35
|
find <termo> Busca simbolos no codigo (funcoes, tipos, etc)
|
|
36
|
+
describe <termo> Busca areas por descricao em linguagem natural
|
|
33
37
|
|
|
34
38
|
AREAS:
|
|
35
39
|
areas Lista todas as areas/dominios do projeto
|
|
@@ -83,6 +87,7 @@ EXEMPLOS:
|
|
|
83
87
|
ai-tool find useAuth # Busca definicao e usos
|
|
84
88
|
ai-tool find User --type=type # Busca apenas tipos
|
|
85
89
|
ai-tool find login --area=auth # Busca na area auth
|
|
90
|
+
ai-tool describe cache # Busca areas por descricao
|
|
86
91
|
ai-tool areas
|
|
87
92
|
ai-tool area auth
|
|
88
93
|
ai-tool area auth --type=hook
|
|
@@ -108,7 +113,7 @@ async function main() {
|
|
|
108
113
|
}
|
|
109
114
|
}
|
|
110
115
|
if (flags.mcp) {
|
|
111
|
-
const { startMcpServer } = await import("./server-
|
|
116
|
+
const { startMcpServer } = await import("./server-CMKKGDE4.js");
|
|
112
117
|
await startMcpServer();
|
|
113
118
|
return;
|
|
114
119
|
}
|
|
@@ -222,6 +227,15 @@ async function main() {
|
|
|
222
227
|
trigger: flags.trigger
|
|
223
228
|
});
|
|
224
229
|
break;
|
|
230
|
+
case "describe":
|
|
231
|
+
if (!target) {
|
|
232
|
+
console.error("\u274C Erro: termo de busca \xE9 obrigat\xF3rio para o comando describe");
|
|
233
|
+
console.error(" Exemplo: ai-tool describe cache");
|
|
234
|
+
console.error(" Exemplo: ai-tool describe login");
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
result = await describe(target, { format, cwd });
|
|
238
|
+
break;
|
|
225
239
|
default:
|
|
226
240
|
console.error(`\u274C Comando desconhecido: ${command}`);
|
|
227
241
|
console.error(" Use 'ai-tool --help' para ver comandos dispon\xEDveis.");
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describe
|
|
3
|
+
} from "./chunk-2ONTJF3R.js";
|
|
1
4
|
import {
|
|
2
5
|
VERSION,
|
|
3
6
|
area,
|
|
@@ -7,15 +10,11 @@ import {
|
|
|
7
10
|
context,
|
|
8
11
|
dead,
|
|
9
12
|
find,
|
|
10
|
-
findSimilar,
|
|
11
|
-
formatOutput,
|
|
12
13
|
functions,
|
|
13
14
|
impact,
|
|
14
15
|
map,
|
|
15
|
-
parseCommandOptions,
|
|
16
|
-
readConfig,
|
|
17
16
|
suggest
|
|
18
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-6ZWPDZDS.js";
|
|
19
18
|
|
|
20
19
|
// src/mcp/server.ts
|
|
21
20
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -23,143 +22,6 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
23
22
|
|
|
24
23
|
// src/mcp/tools.ts
|
|
25
24
|
import { z } from "zod";
|
|
26
|
-
|
|
27
|
-
// src/commands/describe.ts
|
|
28
|
-
async function describe(query, options = {}) {
|
|
29
|
-
const { cwd, format } = parseCommandOptions(options);
|
|
30
|
-
if (!query || query.trim().length === 0) {
|
|
31
|
-
throw new Error("Query \xE9 obrigat\xF3ria. Exemplo: ai-tool describe 'autentica\xE7\xE3o'");
|
|
32
|
-
}
|
|
33
|
-
try {
|
|
34
|
-
const config = readConfig(cwd);
|
|
35
|
-
const normalizedQuery = query.toLowerCase().trim();
|
|
36
|
-
const candidates = Object.entries(config.areas).map(([id, area2]) => ({
|
|
37
|
-
id,
|
|
38
|
-
name: area2.name,
|
|
39
|
-
description: area2.description || ""
|
|
40
|
-
}));
|
|
41
|
-
const matches = findAreaMatches(normalizedQuery, candidates, config);
|
|
42
|
-
const suggestions = [];
|
|
43
|
-
if (matches.length === 0) {
|
|
44
|
-
const similarAreaIds = findSimilar(
|
|
45
|
-
normalizedQuery,
|
|
46
|
-
candidates.map((c) => c.id),
|
|
47
|
-
{ maxDistance: 2, limit: 3 }
|
|
48
|
-
);
|
|
49
|
-
const similarNames = findSimilar(
|
|
50
|
-
normalizedQuery,
|
|
51
|
-
candidates.map((c) => c.name),
|
|
52
|
-
{ maxDistance: 2, limit: 3 }
|
|
53
|
-
);
|
|
54
|
-
suggestions.push(
|
|
55
|
-
...similarAreaIds.map((id) => `\u2192 ai-tool describe ${id}`),
|
|
56
|
-
...similarNames.map((name) => `\u2192 ai-tool describe "${name}"`)
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
const result = {
|
|
60
|
-
version: "1.0.0",
|
|
61
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
62
|
-
query,
|
|
63
|
-
areas: matches,
|
|
64
|
-
suggestions: suggestions.length > 0 ? suggestions : void 0
|
|
65
|
-
};
|
|
66
|
-
return formatOutput(result, format, formatDescribeText);
|
|
67
|
-
} catch (error) {
|
|
68
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
69
|
-
throw new Error(`Erro ao executar describe: ${message}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
function findAreaMatches(normalizedQuery, candidates, config) {
|
|
73
|
-
const matches = [];
|
|
74
|
-
for (const candidate of candidates) {
|
|
75
|
-
const searchableText = `${candidate.id} ${candidate.name} ${candidate.description}`.toLowerCase();
|
|
76
|
-
const hasDirectMatch = searchableText.includes(normalizedQuery);
|
|
77
|
-
const queryWords = normalizedQuery.split(/\s+/);
|
|
78
|
-
const allWordsMatch = queryWords.every((word) => searchableText.includes(word));
|
|
79
|
-
if (hasDirectMatch || allWordsMatch) {
|
|
80
|
-
const areaFiles = getAreaFiles(candidate.id, config);
|
|
81
|
-
const score = calculateRelevanceScore(normalizedQuery, searchableText);
|
|
82
|
-
matches.push({
|
|
83
|
-
id: candidate.id,
|
|
84
|
-
name: candidate.name,
|
|
85
|
-
description: candidate.description || "Sem descri\xE7\xE3o",
|
|
86
|
-
files: areaFiles,
|
|
87
|
-
fileCount: areaFiles.length,
|
|
88
|
-
score
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return matches.sort((a, b) => a.score - b.score);
|
|
93
|
-
}
|
|
94
|
-
function getAreaFiles(areaId, config) {
|
|
95
|
-
const files = [];
|
|
96
|
-
const areaConfig = config.areas[areaId];
|
|
97
|
-
if (areaConfig?.patterns) {
|
|
98
|
-
files.push(`[Use 'ai-tool area ${areaId}' para ver arquivos completos]`);
|
|
99
|
-
}
|
|
100
|
-
return files;
|
|
101
|
-
}
|
|
102
|
-
function calculateRelevanceScore(query, text) {
|
|
103
|
-
if (text.includes(query)) {
|
|
104
|
-
return 0;
|
|
105
|
-
}
|
|
106
|
-
const queryWords = query.split(/\s+/).filter(Boolean);
|
|
107
|
-
const allWordsPresent = queryWords.every((word) => text.includes(word));
|
|
108
|
-
if (allWordsPresent) {
|
|
109
|
-
return 1;
|
|
110
|
-
}
|
|
111
|
-
return 10;
|
|
112
|
-
}
|
|
113
|
-
function formatDescribeText(result) {
|
|
114
|
-
let out = "";
|
|
115
|
-
if (result.areas.length === 0) {
|
|
116
|
-
out += `\u274C Nenhuma \xE1rea encontrada para: "${result.query}"
|
|
117
|
-
|
|
118
|
-
`;
|
|
119
|
-
if (result.suggestions && result.suggestions.length > 0) {
|
|
120
|
-
out += `\u{1F4A1} Voc\xEA quis dizer?
|
|
121
|
-
`;
|
|
122
|
-
for (const suggestion of result.suggestions) {
|
|
123
|
-
out += ` ${suggestion}
|
|
124
|
-
`;
|
|
125
|
-
}
|
|
126
|
-
out += `
|
|
127
|
-
`;
|
|
128
|
-
}
|
|
129
|
-
out += `\u{1F4D6} Dica: Use 'ai-tool areas' para listar todas as \xE1reas dispon\xEDveis`;
|
|
130
|
-
return out;
|
|
131
|
-
}
|
|
132
|
-
out += `\u{1F50D} Busca: "${result.query}"
|
|
133
|
-
|
|
134
|
-
`;
|
|
135
|
-
for (const area2 of result.areas) {
|
|
136
|
-
out += `## ${area2.name} (${area2.id})
|
|
137
|
-
`;
|
|
138
|
-
out += `${area2.description}
|
|
139
|
-
`;
|
|
140
|
-
out += `\u{1F4C1} ${area2.fileCount} arquivo(s)
|
|
141
|
-
|
|
142
|
-
`;
|
|
143
|
-
if (area2.files.length > 0) {
|
|
144
|
-
out += `Arquivos:
|
|
145
|
-
`;
|
|
146
|
-
for (const file of area2.files) {
|
|
147
|
-
out += ` \u2022 ${file}
|
|
148
|
-
`;
|
|
149
|
-
}
|
|
150
|
-
out += "\n";
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
out += `\u{1F4D6} Pr\xF3ximos passos:
|
|
154
|
-
`;
|
|
155
|
-
out += ` \u2192 ai-tool area <id> - ver detalhes de uma \xE1rea
|
|
156
|
-
`;
|
|
157
|
-
out += ` \u2192 ai-tool context --area=<id> - contexto completo de uma \xE1rea
|
|
158
|
-
`;
|
|
159
|
-
return out;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// src/mcp/tools.ts
|
|
163
25
|
function registerAllTools(server2) {
|
|
164
26
|
server2.registerTool(
|
|
165
27
|
"aitool_project_map",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justmpm/ai-tool",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
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",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"build": "tsup src/index.ts src/cli.ts --format esm --dts --clean",
|
|
40
40
|
"dev": "tsup src/index.ts src/cli.ts --format esm --dts --watch",
|
|
41
41
|
"prepublishOnly": "npm run build",
|
|
42
|
-
"test": "
|
|
42
|
+
"test": "tsx --test",
|
|
43
43
|
"typecheck": "tsc --noEmit"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/node": "^22.15.21",
|
|
55
|
+
"tsx": "^4.19.0",
|
|
55
56
|
"tsup": "^8.5.0",
|
|
56
57
|
"typescript": "^5.8.3"
|
|
57
58
|
},
|