feat: criado repositório jhonny-editor no Gitea

criado repositório jhonny-editor no Gitea
adicionado script admin/deploy.command com commit automático
atualizado admin/DEV-NOTES.md com template limpo

Resumo:
- 8 arquivos alterados
- 2 novos
- 6 modificados
- 0 removidos

 6 files changed, 33 insertions(+), 3 deletions(-)

Arquivos:
  - .agents/skills/boas-praticas-oo/SKILL.md
  - .claude/settings.json
  - AGENTS.md
  - CLAUDE.md
  - CODING_STANDARDS.md
  - code/engine/executar_scanner.py
  - .claude/hooks/
  - code/engine/testes/test_executar_scanner.py
This commit is contained in:
João Henrique
2026-09-08 17:19:03 -04:00
parent ee181360c8
commit b2a8b1f24c
8 changed files with 273 additions and 3 deletions
+211
View File
@@ -0,0 +1,211 @@
#!/usr/bin/env python3
"""
Hook PostToolUse: verifica conformidade básica com
`.agents/skills/boas-praticas-oo/SKILL.md` em arquivos Python de
`code/engine/` recém-escritos ou editados por Claude Code.
Este script não persiste dados e não é importado pelo `engine` — é uma
ferramenta de infraestrutura de desenvolvimento, acionada pelo hook
configurado em `.claude/settings.json`.
Ele recebe no stdin o JSON de entrada do hook (evento PostToolUse, tool
Write ou Edit), lê o arquivo do disco (já gravado nesse ponto) e checa,
via `ast`, se o arquivo tem: docstring de módulo, docstring em toda
classe pública, docstring em toda função/método público, e nenhum
`except` genérico demais (`except:` puro, ou `except Exception:` cujo
corpo é só `pass`).
Ele não verifica nomenclatura em PT-BR (a checagem por regex geraria
falsos positivos demais para identificadores técnicos/externos) — isso
continua sendo responsabilidade da revisão humana ou do `/code-review`.
Quando encontra violações, devolve um JSON com `"decision": "block"` e
`"reason"` explicando o que falta, para que o próprio Claude Code corrija
antes de considerar a tarefa concluída. Nunca bloqueia a gravação em si
(o arquivo já foi escrito quando o hook roda) — o bloqueio é apenas o
sinal que leva o agente a corrigir na mesma tarefa.
"""
from __future__ import annotations
import ast
import json
import sys
from pathlib import Path
def arquivo_e_alvo_da_verificacao(caminho: Path) -> bool:
"""
Decide se um caminho deve ser verificado por esta checagem.
Parâmetros:
caminho: Caminho absoluto do arquivo editado ou criado.
Retorna:
True quando o arquivo é um `.py` dentro de `code/engine/`.
"""
partes = caminho.parts
return caminho.suffix == ".py" and "code" in partes and "engine" in partes
def arquivo_e_de_teste(caminho: Path) -> bool:
"""
Decide se um arquivo é um módulo de teste, onde funções/métodos de
teste ficam isentos da exigência de docstring (o nome do teste já
documenta o comportamento, convenção adotada pela skill `tdd`).
Parâmetros:
caminho: Caminho absoluto do arquivo.
Retorna:
True quando o arquivo está em `code/engine/testes/` ou seu nome
começa com ``test_``.
"""
return "testes" in caminho.parts or caminho.name.startswith("test_")
def coletar_violacoes_de_docstring(arvore: ast.Module, e_arquivo_de_teste: bool) -> list[str]:
"""
Coleta violações de docstring obrigatória em módulo, classes e funções públicas.
Parâmetros:
arvore: AST do arquivo já analisado.
e_arquivo_de_teste: Quando True, não exige docstring em funções
e métodos (apenas em módulo e classes) — testes se
documentam pelo nome.
Retorna:
Lista de mensagens de violação, uma por item sem docstring.
"""
violacoes: list[str] = []
if not ast.get_docstring(arvore):
violacoes.append("Falta docstring de módulo no topo do arquivo.")
for no in arvore.body:
if isinstance(no, ast.ClassDef) and not no.name.startswith("_"):
if not ast.get_docstring(no):
violacoes.append(f"Linha {no.lineno}: classe '{no.name}' sem docstring.")
if not e_arquivo_de_teste:
violacoes.extend(_violacoes_de_metodos(no))
elif isinstance(no, (ast.FunctionDef, ast.AsyncFunctionDef)) and not e_arquivo_de_teste:
if not no.name.startswith("_") and not ast.get_docstring(no):
violacoes.append(f"Linha {no.lineno}: função '{no.name}' sem docstring.")
return violacoes
def _violacoes_de_metodos(classe: ast.ClassDef) -> list[str]:
"""
Coleta violações de docstring nos métodos públicos de uma classe.
Parâmetros:
classe: Nó da classe cujos métodos serão verificados.
Retorna:
Lista de mensagens de violação para métodos públicos sem docstring.
"""
violacoes = []
for membro in classe.body:
if isinstance(membro, (ast.FunctionDef, ast.AsyncFunctionDef)):
if not membro.name.startswith("_") and not ast.get_docstring(membro):
violacoes.append(
f"Linha {membro.lineno}: método '{classe.name}.{membro.name}' sem docstring.")
return violacoes
def coletar_violacoes_de_except_generico(arvore: ast.Module) -> list[str]:
"""
Coleta usos de `except` genéricos demais: bare `except:` e
`except Exception:` cujo corpo é só `pass`.
Parâmetros:
arvore: AST do arquivo já analisado.
Retorna:
Lista de mensagens de violação, uma por `except` problemático.
"""
violacoes: list[str] = []
for no in ast.walk(arvore):
if not isinstance(no, ast.ExceptHandler):
continue
e_bare = no.type is None
e_exception_generica = isinstance(no.type, ast.Name) and no.type.id == "Exception"
corpo_e_so_pass = len(no.body) == 1 and isinstance(no.body[0], ast.Pass)
if e_bare:
violacoes.append(
f"Linha {no.lineno}: 'except:' genérico captura tudo, inclusive "
"KeyboardInterrupt/SystemExit — use uma exceção específica.")
elif e_exception_generica and corpo_e_so_pass:
violacoes.append(
f"Linha {no.lineno}: 'except Exception: pass' engole o erro "
"silenciosamente — trate, registre ou levante uma exceção específica.")
return violacoes
def verificar(caminho: Path) -> list[str]:
"""
Executa todas as checagens de boas práticas sobre um arquivo Python.
Parâmetros:
caminho: Caminho absoluto do arquivo a verificar.
Retorna:
Lista de mensagens de violação encontradas. Lista vazia quando o
arquivo está conforme, o arquivo não existe mais, ou o conteúdo
tem erro de sintaxe (não é responsabilidade deste hook apontar
erros de sintaxe).
"""
try:
codigo_fonte = caminho.read_text(encoding="utf-8")
except OSError:
return []
try:
arvore = ast.parse(codigo_fonte, filename=str(caminho))
except SyntaxError:
return []
e_arquivo_de_teste = arquivo_e_de_teste(caminho)
return (
coletar_violacoes_de_docstring(arvore, e_arquivo_de_teste)
+ coletar_violacoes_de_except_generico(arvore)
)
def main() -> None:
"""
Ponto de entrada do hook: lê o evento no stdin e reporta violações encontradas.
Não gera efeitos colaterais além de imprimir no stdout o JSON de
saída esperado pelo contrato de hooks do Claude Code. Sempre encerra
com código de saída 0 — o "bloqueio" é comunicado via
``{"decision": "block"}``, não via código de saída, porque o arquivo
já foi gravado quando este hook roda (evento PostToolUse).
"""
try:
entrada = json.load(sys.stdin)
except (json.JSONDecodeError, ValueError):
return
caminho_bruto = (entrada.get("tool_input") or {}).get("file_path")
if not caminho_bruto:
return
caminho = Path(caminho_bruto).resolve()
if not arquivo_e_alvo_da_verificacao(caminho):
return
violacoes = verificar(caminho)
if not violacoes:
return
motivo = (
f"Padrões de código (.agents/skills/boas-praticas-oo/SKILL.md) "
f"não atendidos em {caminho.name}:\n- " + "\n- ".join(violacoes)
)
print(json.dumps({"decision": "block", "reason": motivo}, ensure_ascii=False))
if __name__ == "__main__":
main()
+5
View File
@@ -8,6 +8,11 @@
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR/rag/reindex_hook.sh\"",
"async": true
},
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/verificar_boas_praticas.py\"",
"statusMessage": "Verificando boas práticas OO (PT-BR, docstrings, exceções)..."
}
]
}