From b2a8b1f24c5c7f6ea2199de44561c52a2b44ee77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Henrique?= Date: Tue, 8 Sep 2026 17:19:03 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20criado=20reposit=C3=B3rio=20jhonny-edit?= =?UTF-8?q?or=20no=20Gitea?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .agents/skills/boas-praticas-oo/SKILL.md | 5 + .claude/hooks/verificar_boas_praticas.py | 211 ++++++++++++++++++++ .claude/settings.json | 5 + AGENTS.md | 2 +- CLAUDE.md | 15 +- CODING_STANDARDS.md | 2 +- code/engine/executar_scanner.py | 7 + code/engine/testes/test_executar_scanner.py | 29 +++ 8 files changed, 273 insertions(+), 3 deletions(-) create mode 100755 .claude/hooks/verificar_boas_praticas.py create mode 100644 code/engine/testes/test_executar_scanner.py diff --git a/.agents/skills/boas-praticas-oo/SKILL.md b/.agents/skills/boas-praticas-oo/SKILL.md index 30f47b6..635d422 100644 --- a/.agents/skills/boas-praticas-oo/SKILL.md +++ b/.agents/skills/boas-praticas-oo/SKILL.md @@ -1,3 +1,8 @@ +--- +name: boas-praticas-oo +description: Padrões obrigatórios de POO, nomenclatura em PT-BR e documentação para todo código Python de code/engine/ neste projeto (Jhonny). Use SEMPRE — antes de criar, editar, corrigir, refatorar ou revisar qualquer classe, função ou módulo Python do engine, mesmo sem pedido explícito do usuário. Não se aplica a outros projetos. +--- + # Skill — Boas práticas gerais para desenvolvimento Python orientado a objetos ## 1. Objetivo diff --git a/.claude/hooks/verificar_boas_praticas.py b/.claude/hooks/verificar_boas_praticas.py new file mode 100755 index 0000000..9979415 --- /dev/null +++ b/.claude/hooks/verificar_boas_praticas.py @@ -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() diff --git a/.claude/settings.json b/.claude/settings.json index 3096461..0bc3de9 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -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)..." } ] } diff --git a/AGENTS.md b/AGENTS.md index abd3859..eb045fe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ ## Convenções obrigatórias de Python / OO -Todo código Python em `code/engine/` deve seguir a skill **`code/engine/skills/boas-praticas-oo.md`** (boas práticas de programação orientada a objetos). Leia-a antes de criar, modificar, corrigir, revisar, refatorar ou ampliar qualquer sistema Python. +Todo código Python em `code/engine/` deve seguir a skill **`.agents/skills/boas-praticas-oo/SKILL.md`** (boas práticas de programação orientada a objetos) — obrigatória, aplicada automaticamente, não apenas quando solicitada. Leia-a antes de criar, modificar, corrigir, revisar, refatorar ou ampliar qualquer sistema Python. Resumo das regras vinculantes: diff --git a/CLAUDE.md b/CLAUDE.md index 5336d7d..5340633 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,11 +22,24 @@ need to see the matched code, `--map` for a file inventory, and `--path`/ tunnel available (`rag/ensure_tunnel.sh` failed) or for exact-string searches it isn't built for (e.g. searching non-code config not indexed). +## Mandatory skill — read before writing any Python in code/engine/ + +**`.agents/skills/boas-praticas-oo/SKILL.md` is not optional.** It is not +triggered by a request — apply it automatically on every Python file created, +edited, fixed, refactored or reviewed under `code/engine/`: PT-BR identifiers +for everything internal, docstrings on every module/class/public method, +single-responsibility classes, explicit dependency injection, specific +exceptions (never bare `except Exception`), input validation, and tests for +the behavior touched. See [CODING_STANDARDS.md](CODING_STANDARDS.md) for the +normative summary, and run its checklist (skill section 41) before calling +any Python change in `code/engine/` done. + ## Available Skills The following skills are available in this project (located in `.agents/skills/`): ### Code Quality & Architecture +- **`boas-praticas-oo`** - **Mandatory**, always-on. OO standards + PT-BR naming for all `code/engine/` Python (see above) - **`/improve-codebase-architecture`** - Scan codebase for deepening opportunities - **`/codebase-design`** - Design deep modules with simple interfaces - **`/domain-modeling`** - Build shared vocabulary and domain model @@ -49,7 +62,7 @@ When working on this project, use these skills to: ## Code Standards -- Follow existing code conventions +- Python in `code/engine/`: follow `.agents/skills/boas-praticas-oo/SKILL.md` and [CODING_STANDARDS.md](CODING_STANDARDS.md) — mandatory, not a suggestion - Use shared domain vocabulary from CONTEXT.md - Write tests before code (TDD) - Review code changes with `/code-review` diff --git a/CODING_STANDARDS.md b/CODING_STANDARDS.md index a3e9a82..0d1453d 100644 --- a/CODING_STANDARDS.md +++ b/CODING_STANDARDS.md @@ -2,7 +2,7 @@ Este documento consolida as convenções obrigatórias de código do projeto. Serve de fonte de verdade para revisões automáticas (eixo *Standards* do `/code-review`) e vale para qualquer agente que escreva ou altere código. -A fonte completa das regras de orientação a objeto é a skill **`code/engine/skills/boas-praticas-oo.md`**. O que está abaixo é o resumo normativo; em caso de dúvida, leia a skill por inteiro. +A fonte completa das regras de orientação a objeto é a skill **`.agents/skills/boas-praticas-oo/SKILL.md`**. O que está abaixo é o resumo normativo; em caso de dúvida, leia a skill por inteiro. ## Escopo diff --git a/code/engine/executar_scanner.py b/code/engine/executar_scanner.py index b756f26..a0a2f57 100644 --- a/code/engine/executar_scanner.py +++ b/code/engine/executar_scanner.py @@ -5,9 +5,16 @@ import json import math import os from pathlib import Path +import sys import tempfile import re +# O painel chama este arquivo diretamente, portanto o Python inclui apenas +# ``code/engine`` no caminho de importação. O pacote público fica em ``code``. +CAMINHO_DO_CODIGO = Path(__file__).resolve().parent.parent +if str(CAMINHO_DO_CODIGO) not in sys.path: + sys.path.insert(0, str(CAMINHO_DO_CODIGO)) + from engine.integracoes.premiere.conversores import ConversorDeTimeline from engine.scanner import ConfiguracaoVisualDoScanner, criar_detector_apple_vision, gerar_relatorio_visual from engine.scanner.transcricao_da_timeline import TranscricaoDaTimeline diff --git a/code/engine/testes/test_executar_scanner.py b/code/engine/testes/test_executar_scanner.py new file mode 100644 index 0000000..b9b86ed --- /dev/null +++ b/code/engine/testes/test_executar_scanner.py @@ -0,0 +1,29 @@ +"""Testes do ponto de entrada usado pelo painel CEP para o Scanner.""" + +import subprocess +import sys +from pathlib import Path +import unittest + + +class TesteDoPontoDeEntradaDoScanner(unittest.TestCase): + """Garante que o Scanner possa ser iniciado pelo caminho usado no painel.""" + + def test_ponto_de_entrada_direto_carrega_o_pacote_engine(self): + """Executa o arquivo diretamente sem falhar ao importar ``engine``.""" + caminho_do_script = Path(__file__).resolve().parents[1] / "executar_scanner.py" + resultado = subprocess.run( + [sys.executable, str(caminho_do_script), "--help"], + cwd=caminho_do_script.parent.parent, + capture_output=True, + text=True, + check=False, + ) + + self.assertEqual(resultado.returncode, 0, resultado.stderr) + self.assertIn("usage:", resultado.stdout) + self.assertNotIn("No module named 'engine'", resultado.stderr) + + +if __name__ == "__main__": + unittest.main()