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:
@@ -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
|
# Skill — Boas práticas gerais para desenvolvimento Python orientado a objetos
|
||||||
|
|
||||||
## 1. Objetivo
|
## 1. Objetivo
|
||||||
|
|||||||
Executable
+211
@@ -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()
|
||||||
@@ -8,6 +8,11 @@
|
|||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "\"$CLAUDE_PROJECT_DIR/rag/reindex_hook.sh\"",
|
"command": "\"$CLAUDE_PROJECT_DIR/rag/reindex_hook.sh\"",
|
||||||
"async": true
|
"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)..."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## Convenções obrigatórias de Python / OO
|
## 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:
|
Resumo das regras vinculantes:
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
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).
|
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
|
## Available Skills
|
||||||
|
|
||||||
The following skills are available in this project (located in `.agents/skills/`):
|
The following skills are available in this project (located in `.agents/skills/`):
|
||||||
|
|
||||||
### Code Quality & Architecture
|
### 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
|
- **`/improve-codebase-architecture`** - Scan codebase for deepening opportunities
|
||||||
- **`/codebase-design`** - Design deep modules with simple interfaces
|
- **`/codebase-design`** - Design deep modules with simple interfaces
|
||||||
- **`/domain-modeling`** - Build shared vocabulary and domain model
|
- **`/domain-modeling`** - Build shared vocabulary and domain model
|
||||||
@@ -49,7 +62,7 @@ When working on this project, use these skills to:
|
|||||||
|
|
||||||
## Code Standards
|
## 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
|
- Use shared domain vocabulary from CONTEXT.md
|
||||||
- Write tests before code (TDD)
|
- Write tests before code (TDD)
|
||||||
- Review code changes with `/code-review`
|
- Review code changes with `/code-review`
|
||||||
|
|||||||
+1
-1
@@ -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.
|
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
|
## Escopo
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,16 @@ import json
|
|||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import re
|
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.integracoes.premiere.conversores import ConversorDeTimeline
|
||||||
from engine.scanner import ConfiguracaoVisualDoScanner, criar_detector_apple_vision, gerar_relatorio_visual
|
from engine.scanner import ConfiguracaoVisualDoScanner, criar_detector_apple_vision, gerar_relatorio_visual
|
||||||
from engine.scanner.transcricao_da_timeline import TranscricaoDaTimeline
|
from engine.scanner.transcricao_da_timeline import TranscricaoDaTimeline
|
||||||
|
|||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user