minions-ai-agents/antigravity_brain_export/tools/scanner_reusability.py

122 lines
5.0 KiB
Python

import os
import re
import pathspec
def scan_for_reusability(start_path='.', output_file='docs/05_Reusabilidade_Ativos.md', gitignore_file='.gitignore'):
"""
Varre o projeto em busca de Classes, Funções Exportadas e Web Components.
Gera um relatório Markdown com JSDocs extraídos.
"""
# Configurações de Filtro
ignore_patterns = ['.git', '.agent', 'node_modules', 'venv', 'dist', 'build', '*.min.js']
if os.path.exists(gitignore_file):
with open(gitignore_file, 'r', encoding='utf-8') as f:
ignore_patterns.extend(f.read().splitlines())
spec = pathspec.PathSpec.from_lines('gitwildmatch', ignore_patterns)
# Regex patterns para identificar componentes reutilizáveis (JS/TS)
patterns = {
'Class': r'export\s+class\s+(\w+)',
'Function': r'export\s+function\s+(\w+)',
'WebComponent': r'customElements\.define\([\'"](.*?)[\'"]',
'Vue/React': r'export\s+default\s+\{\s*name:\s*[\'"](\w+)[\'"]'
}
# Regex para capturar comentários JSDoc imediatamente antes
doc_pattern = r'/\*\*(.*?)\*/\s*'
inventory = []
print("🕵️ Iniciando varredura de reusabilidade...")
for root, dirs, files in os.walk(start_path):
# Filtra pastas ignoradas
dirs[:] = [d for d in dirs if not spec.match_file(os.path.join(root, d))]
for file in files:
file_path = os.path.join(root, file)
# Analisa apenas arquivos .js, .ts, .jsx, .vue
if not file.endswith(('.js', '.ts', '.jsx', '.vue')) or spec.match_file(file_path):
continue
try:
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# Procura por definições
for tipo, regex in patterns.items():
matches = re.finditer(regex, content)
for match in matches:
name = match.group(1)
# Tenta pegar o JSDoc antes da definição
start_index = match.start()
preceding_text = content[:start_index].strip()
description = "Sem documentação."
# Verifica se termina com um bloco de comentário
# Usamos rfind para garantir que pegamos apenas o ÚLTIMO bloco de comentário antes da definição
last_doc_start = preceding_text.rfind('/**')
if last_doc_start != -1:
potential_doc = preceding_text[last_doc_start:]
doc_match = re.search(r'/\*\*(.*?)\*/$', potential_doc, re.DOTALL)
if doc_match:
# Limpa os asteriscos do JSDoc
raw_doc = doc_match.group(1)
clean_doc = '\n'.join([line.strip().lstrip('*').strip() for line in raw_doc.splitlines() if line.strip()])
description = clean_doc if clean_doc else "Documentação vazia."
inventory.append({
'file': file_path,
'type': tipo,
'name': name,
'desc': description,
'size': os.path.getsize(file_path)
})
except Exception as e:
print(f"⚠️ Erro ao ler {file_path}: {e}")
# Gera o Relatório Markdown
return generate_markdown_report(inventory, output_file)
def generate_markdown_report(inventory, output_file):
os.makedirs(os.path.dirname(output_file), exist_ok=True)
md = [
"# ♻️ Catálogo de Reusabilidade",
f"> Gerado automaticamente. Total de ativos encontrados: **{len(inventory)}**",
"",
"## 🧩 Componentes e Módulos",
""
]
# Agrupa por arquivo para ficar organizado
files_map = {}
for item in inventory:
if item['file'] not in files_map: files_map[item['file']] = []
files_map[item['file']].append(item)
for file_path, items in files_map.items():
file_size_kb = os.path.getsize(file_path) / 1024
md.append(f"### 📄 `{file_path}` ({file_size_kb:.2f} KB)")
md.append("| Tipo | Nome | Descrição (JSDoc) |")
md.append("| :--- | :--- | :--- |")
for item in items:
desc_short = item['desc'].replace('\n', ' <br> ')
md.append(f"| `{item['type']}` | **{item['name']}** | {desc_short} |")
md.append("")
with open(output_file, 'w', encoding='utf-8') as f:
f.write('\n'.join(md))
return f"✅ Relatório salvo em: {output_file}"
if __name__ == "__main__":
import sys
# Se passar argumento, muda o arquivo de saída
out = sys.argv[1] if len(sys.argv) > 1 else 'docs/05_Reusabilidade_Ativos.md'
print(scan_for_reusability(output_file=out))