New skills covering 10 categories: **Security & Audit**: 007 (STRIDE/PASTA/OWASP), cred-omega (secrets management) **AI Personas**: Karpathy, Hinton, Sutskever, LeCun (4 sub-skills), Altman, Musk, Gates, Jobs, Buffett **Multi-agent Orchestration**: agent-orchestrator, task-intelligence, multi-advisor **Code Analysis**: matematico-tao (Terence Tao-inspired mathematical code analysis) **Social & Messaging**: Instagram Graph API, Telegram Bot, WhatsApp Cloud API, social-orchestrator **Image Generation**: AI Studio (Gemini), Stability AI, ComfyUI Gateway, image-studio router **Brazilian Domain**: 6 auction specialist modules, 2 legal advisors, auctioneers data scraper **Product & Growth**: design, invention, monetization, analytics, growth engine **DevOps & LLM Ops**: Docker/CI-CD/AWS, RAG/embeddings/fine-tuning **Skill Governance**: installer, sentinel auditor, context management Each skill includes: - Standardized YAML frontmatter (name, description, risk, source, tags, tools) - Structured sections (Overview, When to Use, How it Works, Best Practices) - Python scripts and reference documentation where applicable - Cross-platform compatibility (Claude Code, Antigravity, Cursor, Gemini CLI, Codex CLI) Co-authored-by: ProgramadorBrasil <214873561+ProgramadorBrasil@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
248 lines
9.6 KiB
Python
248 lines
9.6 KiB
Python
"""
|
|
Analyzer de qualidade de codigo.
|
|
|
|
Usa AST (stdlib) para medir complexidade ciclomatica, tamanho de funcoes,
|
|
cobertura de docstrings e padroes de error handling.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import ast
|
|
from pathlib import Path
|
|
from typing import Any, Dict, List, Tuple
|
|
|
|
from config import (
|
|
MAX_CYCLOMATIC_COMPLEXITY,
|
|
MAX_FILE_LINES,
|
|
MAX_FUNCTION_LINES,
|
|
PENALTY_BARE_EXCEPT,
|
|
PENALTY_BROAD_EXCEPT,
|
|
PENALTY_HIGH_COMPLEXITY,
|
|
PENALTY_LONG_FILE,
|
|
PENALTY_LONG_FUNCTION,
|
|
PENALTY_NO_DOCSTRING,
|
|
)
|
|
|
|
|
|
def _cyclomatic_complexity(node: ast.AST) -> int:
|
|
"""Calcula complexidade ciclomatica de uma funcao/metodo."""
|
|
complexity = 1 # base
|
|
for child in ast.walk(node):
|
|
if isinstance(child, (ast.If, ast.IfExp)):
|
|
complexity += 1
|
|
elif isinstance(child, (ast.For, ast.AsyncFor, ast.While)):
|
|
complexity += 1
|
|
elif isinstance(child, ast.ExceptHandler):
|
|
complexity += 1
|
|
elif isinstance(child, (ast.With, ast.AsyncWith)):
|
|
complexity += 1
|
|
elif isinstance(child, ast.BoolOp):
|
|
# cada and/or adiciona um path
|
|
complexity += len(child.values) - 1
|
|
elif isinstance(child, ast.Assert):
|
|
complexity += 1
|
|
return complexity
|
|
|
|
|
|
def _check_except_patterns(node: ast.AST) -> List[Dict[str, Any]]:
|
|
"""Verifica padroes de except (bare except, broad except)."""
|
|
issues = []
|
|
for child in ast.walk(node):
|
|
if isinstance(child, ast.ExceptHandler):
|
|
if child.type is None:
|
|
issues.append({
|
|
"type": "bare_except",
|
|
"line": child.lineno,
|
|
"severity": "high",
|
|
})
|
|
elif isinstance(child.type, ast.Name) and child.type.id == "Exception":
|
|
# Verificar se tem logging no corpo
|
|
has_log = False
|
|
for stmt in ast.walk(child):
|
|
if isinstance(stmt, ast.Call):
|
|
func = stmt.func
|
|
if isinstance(func, ast.Attribute) and func.attr in (
|
|
"error", "warning", "exception", "critical"
|
|
):
|
|
has_log = True
|
|
break
|
|
if isinstance(func, ast.Name) and func.id == "print":
|
|
has_log = True
|
|
break
|
|
if not has_log:
|
|
issues.append({
|
|
"type": "broad_except_no_log",
|
|
"line": child.lineno,
|
|
"severity": "medium",
|
|
})
|
|
return issues
|
|
|
|
|
|
def analyze(skill_data: Dict[str, Any]) -> Tuple[float, List[Dict[str, Any]]]:
|
|
"""
|
|
Analisa qualidade de codigo de uma skill.
|
|
Retorna (score, findings).
|
|
"""
|
|
score = 100.0
|
|
findings: List[Dict[str, Any]] = []
|
|
skill_name = skill_data["name"]
|
|
skill_path = Path(skill_data["path"])
|
|
|
|
total_functions = 0
|
|
functions_with_docs = 0
|
|
|
|
for rel_path in skill_data.get("python_files", []):
|
|
filepath = skill_path / rel_path
|
|
if not filepath.exists():
|
|
continue
|
|
|
|
try:
|
|
source = filepath.read_text(encoding="utf-8", errors="replace")
|
|
tree = ast.parse(source, filename=str(filepath))
|
|
except SyntaxError as e:
|
|
findings.append({
|
|
"skill_name": skill_name,
|
|
"dimension": "code_quality",
|
|
"severity": "high",
|
|
"category": "syntax_error",
|
|
"title": f"Erro de sintaxe em {rel_path}",
|
|
"description": str(e),
|
|
"file_path": rel_path,
|
|
"line_number": getattr(e, "lineno", None),
|
|
"recommendation": "Corrigir o erro de sintaxe",
|
|
"effort": "low",
|
|
"impact": "high",
|
|
})
|
|
score -= 15
|
|
continue
|
|
|
|
lines = source.splitlines()
|
|
file_lines = len(lines)
|
|
|
|
# Arquivo muito longo
|
|
if file_lines > MAX_FILE_LINES:
|
|
findings.append({
|
|
"skill_name": skill_name,
|
|
"dimension": "code_quality",
|
|
"severity": "medium",
|
|
"category": "long_file",
|
|
"title": f"Arquivo longo: {rel_path} ({file_lines} linhas)",
|
|
"description": f"Arquivo excede {MAX_FILE_LINES} linhas. Considerar dividir em modulos menores.",
|
|
"file_path": rel_path,
|
|
"recommendation": "Extrair funcionalidades em modulos separados",
|
|
"effort": "medium",
|
|
"impact": "medium",
|
|
})
|
|
score -= PENALTY_LONG_FILE
|
|
|
|
# Analisar funcoes e classes
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
total_functions += 1
|
|
func_name = node.name
|
|
end_line = getattr(node, "end_lineno", node.lineno)
|
|
func_lines = end_line - node.lineno + 1
|
|
|
|
# Docstring
|
|
has_doc = (
|
|
node.body
|
|
and isinstance(node.body[0], ast.Expr)
|
|
and isinstance(node.body[0].value, (ast.Constant, ast.Str))
|
|
)
|
|
if has_doc:
|
|
functions_with_docs += 1
|
|
else:
|
|
if not func_name.startswith("_"):
|
|
score -= PENALTY_NO_DOCSTRING
|
|
|
|
# Funcao longa
|
|
if func_lines > MAX_FUNCTION_LINES:
|
|
findings.append({
|
|
"skill_name": skill_name,
|
|
"dimension": "code_quality",
|
|
"severity": "medium",
|
|
"category": "long_function",
|
|
"title": f"Funcao longa: {func_name} ({func_lines} linhas) em {rel_path}",
|
|
"file_path": rel_path,
|
|
"line_number": node.lineno,
|
|
"recommendation": f"Reduzir {func_name} para < {MAX_FUNCTION_LINES} linhas extraindo sub-funcoes",
|
|
"effort": "medium",
|
|
"impact": "medium",
|
|
})
|
|
score -= PENALTY_LONG_FUNCTION
|
|
|
|
# Complexidade ciclomatica
|
|
complexity = _cyclomatic_complexity(node)
|
|
if complexity > MAX_CYCLOMATIC_COMPLEXITY:
|
|
findings.append({
|
|
"skill_name": skill_name,
|
|
"dimension": "code_quality",
|
|
"severity": "medium",
|
|
"category": "high_complexity",
|
|
"title": f"Alta complexidade: {func_name} (CC={complexity}) em {rel_path}",
|
|
"file_path": rel_path,
|
|
"line_number": node.lineno,
|
|
"recommendation": f"Simplificar {func_name} (CC={complexity} > {MAX_CYCLOMATIC_COMPLEXITY})",
|
|
"effort": "medium",
|
|
"impact": "medium",
|
|
})
|
|
score -= PENALTY_HIGH_COMPLEXITY
|
|
|
|
elif isinstance(node, ast.ClassDef):
|
|
total_functions += 1
|
|
has_doc = (
|
|
node.body
|
|
and isinstance(node.body[0], ast.Expr)
|
|
and isinstance(node.body[0].value, (ast.Constant, ast.Str))
|
|
)
|
|
if has_doc:
|
|
functions_with_docs += 1
|
|
|
|
# Padroes de except
|
|
except_issues = _check_except_patterns(tree)
|
|
for issue in except_issues:
|
|
if issue["type"] == "bare_except":
|
|
findings.append({
|
|
"skill_name": skill_name,
|
|
"dimension": "code_quality",
|
|
"severity": "high",
|
|
"category": "bare_except",
|
|
"title": f"Bare except em {rel_path}:{issue['line']}",
|
|
"file_path": rel_path,
|
|
"line_number": issue["line"],
|
|
"recommendation": "Usar except especifico (ex: except ValueError) em vez de bare except",
|
|
"effort": "low",
|
|
"impact": "high",
|
|
})
|
|
score -= PENALTY_BARE_EXCEPT
|
|
elif issue["type"] == "broad_except_no_log":
|
|
findings.append({
|
|
"skill_name": skill_name,
|
|
"dimension": "code_quality",
|
|
"severity": "medium",
|
|
"category": "broad_except",
|
|
"title": f"except Exception sem logging em {rel_path}:{issue['line']}",
|
|
"file_path": rel_path,
|
|
"line_number": issue["line"],
|
|
"recommendation": "Adicionar logging.error() ou re-raise dentro do except Exception",
|
|
"effort": "low",
|
|
"impact": "medium",
|
|
})
|
|
score -= PENALTY_BROAD_EXCEPT
|
|
|
|
# Bonus/penalidade por cobertura de docstrings
|
|
if total_functions > 0:
|
|
doc_coverage = functions_with_docs / total_functions
|
|
if doc_coverage < 0.3:
|
|
findings.append({
|
|
"skill_name": skill_name,
|
|
"dimension": "code_quality",
|
|
"severity": "low",
|
|
"category": "low_docstring_coverage",
|
|
"title": f"Baixa cobertura de docstrings ({doc_coverage:.0%})",
|
|
"recommendation": "Adicionar docstrings em funcoes publicas para melhorar manutenibilidade",
|
|
"effort": "low",
|
|
"impact": "low",
|
|
})
|
|
|
|
return max(0.0, min(100.0, score)), findings
|