@justmpm/ai-tool 0.9.1 → 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
+ };
@@ -3486,20 +3486,21 @@ function extractTriggerInfo(init, triggerName) {
3486
3486
 
3487
3487
  // src/ts/cache.ts
3488
3488
  function indexProject(cwd) {
3489
- const allFiles = getAllCodeFiles(cwd);
3490
- logger.debug(`Indexando ${allFiles.length} arquivos em ${cwd}`);
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(cwd);
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(cwd, file));
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 = cwd.replace(/\\/g, "/");
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)) {
@@ -5124,7 +5125,10 @@ export {
5124
5125
  clearFirebaseCache,
5125
5126
  getCacheDir,
5126
5127
  isCacheValid,
5128
+ updateCacheMeta,
5127
5129
  invalidateCache,
5130
+ cacheSymbolsIndex,
5131
+ getCachedSymbolsIndex,
5128
5132
  configExists,
5129
5133
  readConfig,
5130
5134
  writeConfig,
@@ -5153,6 +5157,8 @@ export {
5153
5157
  formatInvalidCommand,
5154
5158
  impact,
5155
5159
  suggest,
5160
+ getAllCodeFiles,
5161
+ indexProject,
5156
5162
  context,
5157
5163
  areaContext,
5158
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-CACALWH5.js";
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-NVJXCSJF.js";
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-OMKVOYK7.js");
116
+ const { startMcpServer } = await import("./server-TDSMX4PG.js");
117
117
  await startMcpServer();
118
118
  return;
119
119
  }
package/dist/index.js CHANGED
@@ -44,7 +44,7 @@ import {
44
44
  setFileDescription,
45
45
  suggest,
46
46
  writeConfig
47
- } from "./chunk-NVJXCSJF.js";
47
+ } from "./chunk-J3MX6NK3.js";
48
48
  export {
49
49
  COMMAND_REFERENCE,
50
50
  VERSION,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  describe
3
- } from "./chunk-CACALWH5.js";
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-NVJXCSJF.js";
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.1",
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",
@@ -1,145 +0,0 @@
1
- import {
2
- findSimilar,
3
- formatOutput,
4
- parseCommandOptions,
5
- readConfig
6
- } from "./chunk-NVJXCSJF.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
- };