@justmpm/ai-tool 0.5.4 → 0.6.0

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/README.md CHANGED
@@ -103,6 +103,7 @@ Extrai assinaturas de funcoes e tipos SEM a implementacao.
103
103
  ```bash
104
104
  ai-tool context Button
105
105
  ai-tool context src/hooks/useAuth.ts --format=json
106
+ ai-tool context --area=auth # Contexto consolidado de toda uma area
106
107
  ```
107
108
 
108
109
  **Extrai:**
@@ -113,6 +114,36 @@ ai-tool context src/hooks/useAuth.ts --format=json
113
114
 
114
115
  Ideal para entender rapidamente a API publica de um arquivo.
115
116
 
117
+ **Contexto de Area** (`--area=<nome>`):
118
+ - Tipos e interfaces da area
119
+ - Hooks com parametros e retornos
120
+ - Funcoes principais
121
+ - Componentes React
122
+ - Services e stores
123
+ - Uma chamada = entender toda a feature
124
+
125
+ ### `find` - Busca de Simbolos
126
+
127
+ Busca simbolos no codigo (funcoes, tipos, componentes, hooks, constantes).
128
+
129
+ ```bash
130
+ ai-tool find useAuth # Definicao + usos
131
+ ai-tool find User --type=type # Busca apenas tipos
132
+ ai-tool find login --area=auth # Busca na area auth
133
+ ai-tool find submit --def # Apenas definicoes
134
+ ai-tool find submit --refs # Apenas referencias/usos
135
+ ```
136
+
137
+ **Tipos de simbolos:**
138
+ - `function` - Funcoes e arrow functions
139
+ - `type` - Types, interfaces e enums
140
+ - `const` - Constantes e variaveis
141
+ - `component` - Componentes React (funcao que retorna JSX)
142
+ - `hook` - React hooks (funcao que comeca com `use`)
143
+ - `all` - Todos os tipos (default)
144
+
145
+ **Diferente de grep:** Entende o AST do TypeScript, encontra definicoes reais e onde sao usados.
146
+
116
147
  ### `areas` - Areas/Dominios Funcionais
117
148
 
118
149
  Lista todas as areas funcionais do projeto (auth, dashboard, stripe, etc).
@@ -205,14 +236,16 @@ ai-tool --mcp
205
236
  ```
206
237
 
207
238
  **Tools expostas:**
208
- - `aitool_project_map` - Mapa do projeto
209
- - `aitool_dead_code` - Codigo morto
210
- - `aitool_impact_analysis` - Analise de impacto
211
- - `aitool_suggest_reads` - Sugestao de leitura
212
- - `aitool_file_context` - Contexto do arquivo
213
- - `aitool_list_areas` - Lista areas funcionais
214
- - `aitool_area_detail` - Detalhe de uma area
239
+ - `aitool_project_map` - Mapa do projeto (resumo compacto)
240
+ - `aitool_dead_code` - Detecta codigo morto
241
+ - `aitool_impact_analysis` - Analise de impacto antes de modificar
242
+ - `aitool_suggest_reads` - Sugere arquivos para ler antes de editar
243
+ - `aitool_file_context` - Extrai assinaturas de um arquivo
244
+ - `aitool_list_areas` - Lista areas funcionais do projeto
245
+ - `aitool_area_detail` - Arquivos de uma area especifica
215
246
  - `aitool_areas_init` - Gera config de areas
247
+ - `aitool_area_context` - Contexto consolidado de toda uma area
248
+ - `aitool_find` - Busca simbolos no codigo (definicao + usos)
216
249
 
217
250
  ### Configuracao Claude Code
218
251
 
@@ -250,7 +283,7 @@ Adicione ao `claude_desktop_config.json`:
250
283
  ## Uso Programatico
251
284
 
252
285
  ```typescript
253
- import { map, dead, impact, suggest, context, areas, area, areasInit } from "@justmpm/ai-tool";
286
+ import { map, dead, impact, suggest, context, areaContext, find, areas, area, areasInit } from "@justmpm/ai-tool";
254
287
 
255
288
  // Mapa do projeto (resumo por padrao, full: true para lista completa)
256
289
  const projectMap = await map({ format: "json" });
@@ -268,6 +301,12 @@ const suggestions = await suggest("Button", { limit: 5 });
268
301
  // Contexto do arquivo
269
302
  const fileContext = await context("Button", { format: "json" });
270
303
 
304
+ // Contexto de uma area inteira
305
+ const authContext = await areaContext("auth", { format: "json" });
306
+
307
+ // Busca de simbolos
308
+ const symbolSearch = await find("useAuth", { type: "hook", area: "auth" });
309
+
271
310
  // Areas funcionais
272
311
  const projectAreas = await areas({ format: "json" });
273
312
 
@@ -288,7 +327,10 @@ await areasInit({ force: false });
288
327
  | `--full` | Lista completa (`map`: arquivos, `area`: todos) | `false` |
289
328
  | `--fix` | Remove codigo morto (so `dead`) | `false` |
290
329
  | `--limit=<n>` | Limite de sugestoes (so `suggest`) | `10` |
291
- | `--type=<cat>` | Filtra por categoria (so `area`) | - |
330
+ | `--type=<cat>` | Filtra por categoria (`area`) ou tipo de simbolo (`find`) | - |
331
+ | `--area=<nome>` | Filtra por area (`context`, `find`) | - |
332
+ | `--def` | Mostra apenas definicoes (so `find`) | `false` |
333
+ | `--refs` | Mostra apenas referencias/usos (so `find`) | `false` |
292
334
  | `--mcp` | Inicia servidor MCP | - |
293
335
 
294
336
  ## Categorias de Arquivos
@@ -0,0 +1,219 @@
1
+ import {
2
+ cacheSymbolsIndex,
3
+ detectFileAreas,
4
+ formatFindText,
5
+ getCachedSymbolsIndex,
6
+ indexProject,
7
+ isCacheValid,
8
+ isFileIgnored,
9
+ readConfig,
10
+ updateCacheMeta
11
+ } from "./chunk-UDT7TLSN.js";
12
+
13
+ // src/commands/find.ts
14
+ async function find(query, options = {}) {
15
+ const cwd = options.cwd || process.cwd();
16
+ const format = options.format || "text";
17
+ const filterType = options.type || "all";
18
+ const filterArea = options.area;
19
+ const defOnly = options.def ?? false;
20
+ const refsOnly = options.refs ?? false;
21
+ const useCache = options.cache !== false;
22
+ if (!query || query.trim().length === 0) {
23
+ throw new Error("Query \xE9 obrigat\xF3ria. Exemplo: ai-tool find useAuth");
24
+ }
25
+ try {
26
+ let index;
27
+ let fromCache = false;
28
+ if (useCache && isCacheValid(cwd)) {
29
+ const cached = getCachedSymbolsIndex(cwd);
30
+ if (cached && cached.symbolsByName) {
31
+ index = cached;
32
+ fromCache = true;
33
+ } else {
34
+ index = indexProject(cwd);
35
+ cacheSymbolsIndex(cwd, index);
36
+ updateCacheMeta(cwd);
37
+ }
38
+ } else {
39
+ index = indexProject(cwd);
40
+ if (useCache) {
41
+ cacheSymbolsIndex(cwd, index);
42
+ updateCacheMeta(cwd);
43
+ }
44
+ }
45
+ let allowedFiles = null;
46
+ if (filterArea) {
47
+ const config = readConfig(cwd);
48
+ const areaLower = filterArea.toLowerCase();
49
+ allowedFiles = /* @__PURE__ */ new Set();
50
+ for (const filePath of Object.keys(index.files)) {
51
+ if (isFileIgnored(filePath, config)) continue;
52
+ const fileAreas = detectFileAreas(filePath, config);
53
+ const belongsToArea = fileAreas.some(
54
+ (a) => a.toLowerCase() === areaLower || a.toLowerCase().includes(areaLower)
55
+ );
56
+ if (belongsToArea) {
57
+ allowedFiles.add(filePath);
58
+ }
59
+ }
60
+ if (allowedFiles.size === 0) {
61
+ return format === "json" ? JSON.stringify({ error: `Nenhum arquivo encontrado na \xE1rea "${filterArea}"` }) : `\u274C Nenhum arquivo encontrado na \xE1rea "${filterArea}"`;
62
+ }
63
+ }
64
+ const matches = searchInIndex(index, query, filterType, allowedFiles);
65
+ let definition = null;
66
+ let references = [];
67
+ for (const match of matches) {
68
+ if (match.matchType === "definition") {
69
+ if (!definition) {
70
+ definition = match;
71
+ }
72
+ } else {
73
+ references.push(match);
74
+ }
75
+ }
76
+ if (defOnly) {
77
+ references = [];
78
+ }
79
+ if (refsOnly) {
80
+ definition = null;
81
+ }
82
+ const uniqueFiles = /* @__PURE__ */ new Set();
83
+ if (definition) uniqueFiles.add(definition.file);
84
+ for (const ref of references) {
85
+ uniqueFiles.add(ref.file);
86
+ }
87
+ const result = {
88
+ version: "1.0.0",
89
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
90
+ query,
91
+ filters: {
92
+ type: filterType !== "all" ? filterType : void 0,
93
+ area: filterArea,
94
+ defOnly,
95
+ refsOnly
96
+ },
97
+ definition,
98
+ references,
99
+ summary: {
100
+ definitions: definition ? 1 : 0,
101
+ references: references.length,
102
+ files: uniqueFiles.size
103
+ },
104
+ fromCache
105
+ };
106
+ if (format === "json") {
107
+ return JSON.stringify(result, null, 2);
108
+ }
109
+ return formatFindText(result);
110
+ } catch (error) {
111
+ const message = error instanceof Error ? error.message : String(error);
112
+ throw new Error(`Erro ao executar find: ${message}`);
113
+ }
114
+ }
115
+ function searchInIndex(index, query, filterType, allowedFiles) {
116
+ const matches = [];
117
+ const queryLower = query.toLowerCase();
118
+ const processedSymbols = /* @__PURE__ */ new Set();
119
+ for (const [name, symbols] of Object.entries(index.symbolsByName)) {
120
+ const nameLower = name.toLowerCase();
121
+ if (nameLower === queryLower || nameLower.includes(queryLower)) {
122
+ for (const symbol of symbols) {
123
+ if (allowedFiles && !allowedFiles.has(symbol.file)) continue;
124
+ if (!matchesType(symbol.kind, filterType)) continue;
125
+ const key = `${symbol.file}:${symbol.line}:${symbol.name}`;
126
+ if (processedSymbols.has(key)) continue;
127
+ processedSymbols.add(key);
128
+ const fileData = index.files[symbol.file];
129
+ matches.push({
130
+ file: symbol.file,
131
+ line: symbol.line,
132
+ column: 0,
133
+ code: symbol.signature,
134
+ matchType: "definition",
135
+ symbolType: mapKindToSymbolType(symbol.kind),
136
+ category: fileData?.category || "other"
137
+ });
138
+ }
139
+ }
140
+ }
141
+ for (const [filePath, fileData] of Object.entries(index.files)) {
142
+ if (allowedFiles && !allowedFiles.has(filePath)) continue;
143
+ for (const imp of fileData.imports) {
144
+ for (const spec of imp.specifiers) {
145
+ const specLower = spec.toLowerCase();
146
+ if (specLower === queryLower || specLower.includes(queryLower)) {
147
+ const key = `import:${filePath}:${spec}:${imp.source}`;
148
+ if (processedSymbols.has(key)) continue;
149
+ processedSymbols.add(key);
150
+ matches.push({
151
+ file: filePath,
152
+ line: 1,
153
+ // Imports geralmente estão no topo
154
+ column: 0,
155
+ code: `import { ${spec} } from '${imp.source}'`,
156
+ matchType: "import",
157
+ symbolType: inferSymbolTypeFromName(spec),
158
+ category: fileData.category
159
+ });
160
+ }
161
+ }
162
+ }
163
+ }
164
+ matches.sort((a, b) => {
165
+ const order = { definition: 0, import: 1, usage: 2 };
166
+ const orderDiff = order[a.matchType] - order[b.matchType];
167
+ if (orderDiff !== 0) return orderDiff;
168
+ return a.file.localeCompare(b.file) || a.line - b.line;
169
+ });
170
+ return matches;
171
+ }
172
+ function matchesType(kind, filter) {
173
+ if (filter === "all") return true;
174
+ switch (filter) {
175
+ case "function":
176
+ return kind === "function";
177
+ case "type":
178
+ return kind === "type" || kind === "interface" || kind === "enum";
179
+ case "const":
180
+ return kind === "const";
181
+ case "component":
182
+ return kind === "component";
183
+ case "hook":
184
+ return kind === "hook";
185
+ default:
186
+ return true;
187
+ }
188
+ }
189
+ function mapKindToSymbolType(kind) {
190
+ switch (kind) {
191
+ case "function":
192
+ return "function";
193
+ case "hook":
194
+ return "hook";
195
+ case "component":
196
+ return "component";
197
+ case "type":
198
+ case "interface":
199
+ case "enum":
200
+ return "type";
201
+ case "const":
202
+ return "const";
203
+ default:
204
+ return "function";
205
+ }
206
+ }
207
+ function inferSymbolTypeFromName(name) {
208
+ if (name.startsWith("use") && name.length > 3 && name[3] === name[3].toUpperCase()) {
209
+ return "hook";
210
+ }
211
+ if (name[0] === name[0].toUpperCase()) {
212
+ return "type";
213
+ }
214
+ return "function";
215
+ }
216
+
217
+ export {
218
+ find
219
+ };