From 2464a9057e5b3dd2637c00d4d7881abf5809d38e Mon Sep 17 00:00:00 2001 From: Renan Fernandes Date: Sat, 27 Jun 2026 10:48:43 -0300 Subject: [PATCH 1/4] chore: clean up tracked artifacts, docs, and fix .gitignore --- .coverage | Bin 53248 -> 0 bytes .gitignore | 5 +- BLUEPRINT.md | 104 ----------------------------------------- repomix-output.xml | 113 --------------------------------------------- requirements.txt | 7 --- 5 files changed, 3 insertions(+), 226 deletions(-) delete mode 100644 .coverage delete mode 100644 BLUEPRINT.md delete mode 100644 repomix-output.xml delete mode 100644 requirements.txt diff --git a/.coverage b/.coverage deleted file mode 100644 index 733ee0286dd15dfc1ee18adc425c5d3744696e88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53248 zcmeI4UyR&F9mnlm-|pG#_3Yg$y@pWERhwQ;x!mOxnvj-~UehBKDlTb4^9LHAZ`XHc zvb(<6-Xup!n=NUB-~m(-qzVu`AW$X#y&xzc6p^ojB+JtMrYT91O5Y-V zlPymXB{`r_?uf4xRi)7<4i?#?lAb#tvBSj=uub}${(9lt`bX5y6x{rXI!X!HKmY_l z00dSIfz}>9Kd^SK{M5VIi}mFx)nQg8*^qj?^eJ?;hEBAb^3W^^s(5~tYd_=Ljmo?ejF|RP3o9P!c_NHj>wERF&@*dpLo~Ay zxS{JcOy`7CX@*X9*#?eqXvs$KI$J}srg?*=qmpr59dz1-mnj)%j|dsWw^GTK%7$Lt*c6{3T*67m7vYVkNjxFkZYn*oa>Av3lz~G>K z=BO|fj>npD!(b_*b{Wmm=@m?er>|@`e3Wz>2_HqZn(57oA;U|XQRk2~caE8{1cvSN zfo6JfM$+_swKlFS>0r*(yqRuW6BZLVG(gA~B-v)0jS3$s0dGUaJLdTIoHH7yMsDH# zHY75gaWywEIk=MPESZ2cz7*A3YjgR5b?fA(RAEMOZ^c2r%Y>4iT*i$iNp;5#nttL) z-pNTOCE8vx86$BDHD^;kH!!horRK!>tnoO~+sZ5Xfi-L7Rx~2GU*g}Mu14@P?ICc@ zL~!HkFWBL{dA;uZG$}sRlN-2q%}R=M6|8Z-&z&x1gvwve3i@?&`uv;iQkkD=tNbZv zanmuoM&4w>c6}PMKJQ#7E}D%Vv_mI!>yF8@2xWv}9k&Cs;yaWW&!s{!A~t`mR*{%b zP)g!Mqt*-$W#7qmco$pae9u?Oz5G(HH7Mr?Hg1$#&3FVy&dCbsN}z5hZH*5zU6yrC zV%x~3$V<~DQm1io;2jM4VN?V6h*7uCMCg)S@dc7L%oq4FdrP7ZHV^;-5C8!X009sH z0T2KI5C8!X0D;vHah;{(JdOaD#Kor*lXpPkbFxc`8(59KdvLr8oo-9tafi$A|CiOkJ>8!P1lQOw9Bi0x<>;+00ck)1V8`;KtPf#r8Oa^bHNq8F{^w1 zUp}a`?oLa7*ZRNzvr21wijq6m|9uCP)|ND7C+q*xjMCbbmf+>r|14!Lo$G(&F{O21 z%Ide*|HZ>fYe&k`JJ$btnx?+Y`oA!(w05Sfe%Jb6OW7)y*8l2zpn2E&zc*zs&|d%N zQ)aTX{?8p!THDg<&F=MoPs%jf>wm>oT9auhz3low`yPzZj`e?LN@?AprZXLh^*`SK zhl?B#009sH0T2KI5C8!X009sH0T5W#1hR5gD)RgPGJ9L14>k}00T2KI5C8!X009sH z0T2KI5CDNyNFbXns^a;7!yKznDapfz!GSdB8fyev4`HQQsK51$c zf1vcM|Lz_Cs~lA!N#`i0NRmPK$fZ+#D&@G79s3qf&1Pwr%lFc$Qt=DxwLF)kUy+`% z&)t%v_^5WJk)bTqym&j8@8OwyAI*#^6giyz?SE!PQg)hxb6lR-rp5lv=z#CPsAakE z@};xSZYk$76gM*R$K@VXZ5jT{gM)8B{)W}gElH)0gDC#^Rqg5vhV(+5U`o=7mu@}3 zSwFuyBcA_fCnfe4`xm>&e!{-To@J-m%j}2j&+M!0kL&_F$1o#gHvQZX6zF!1|J`v`). - -### P2 (Funcionalidades Avançadas de Fluxo) -- **Worktree Manager:** Em vez de clonar de novo para testar a branch X, sugerir e criar `git worktrees` (`gitauditor worktree create repo-x feature/teste`). Isso poupa muito espaço de HD e mantém as coisas higienizadas. -- **Templates e Bootstrap:** Ajudar na padronização de novos projetos locais. -- **Suporte a Sparse-Checkout:** Para monorepos que destroem a máquina local, facilitando a vida para baixar só as peças necessárias. - -## 4. Arquitetura de Comandos (Typer) - -A fundação recém construída com o **Typer** permite fatiarmos o CLI de maneira muito elegante. O blueprint de comandos será: - -```bash -# Sincroniza e populo o banco de dados -gitauditor catalog sync - -# Relatórios rápidos e filtragem -gitauditor repos list --tag work --stale -gitauditor catalog health - -# Ações de Limpeza e Organização -gitauditor catalog dedupe --plan -gitauditor repos organize --root ~/Code --dry-run -gitauditor repos prune - -# Agilidade e Fluxo -gitauditor catalog open backend-api -gitauditor worktree create api feature/teste -``` - -## 5. Backlog Futuro: Phase 3 (Semantic AI Layer) - -A próxima grande evolução do GitAuditor foca em integrar Inteligência Artificial (LLMs Locais via Ollama ou APIs Externas plugáveis) não apenas como geradores de mensagens, mas como uma **Camada Semântica Confiável** sobre o inventário Git. - -### Princípios de Design e Governança -Para que a IA adicione valor real sem transformar o produto em uma caixa de surpresas, as seguintes regras guiarão a arquitetura P3: - -1. **Multi-Provider API:** A camada de LLM deve ser agnóstica, permitindo o uso do Ollama local por padrão, mas com portas abertas para provedores externos via chaves de API. -2. **Governança no Catálogo:** O banco de dados (`Repo`) não guardará apenas as respostas. Ele rastreará a proveniência: `ai_model`, `ai_prompt_version`, `ai_updated_at`, `ai_confidence`, `ai_error` e `ai_source_hash`. -3. **Estratégia de Cache e Hash:** A IA só será chamada se o contexto base do repositório mudar. Se o hash do README + manifestos + árvore for o mesmo, lemos do banco. -4. **Validação Estruturada (Pydantic) + Retry:** As saídas da IA deverão seguir schemas JSON estritos e separados por feature, com lógica de retry caso a estrutura venha inválida. -5. **Heurística Antes da IA (Fallback Determinístico):** A classificação (`tag`) começará sempre por regras rígidas e determinísticas (ex: detectar `package.json`). O LLM entra como enriquecedor. Se o LLM falhar, a heurística garante a funcionalidade. -6. **Limites do LLM:** No *repo review*, a IA não substituirá scanners de segurança (como gitleaks). Ela focará em smells qualitativos, arquitetura e dicas humanas. -7. **Contexto Hierárquico Inteligente:** O extrator não vai só cuspir a pasta inteira para o LLM. Ele enviará metadados do Git, a árvore resumida por profundidade, o README truncado, manifestos chaves (`pyproject.toml`, `package.json`, etc) e excluirá arquivos nocivos (`.venv`, `node_modules`). - -### Roadmap Fatiado da Fase P3 - -Para evitar complexidade monstruosa, a implementação será feita em 4 entregas atômicas: - -- **P3.1 | Summarize & Foundation:** - - Adição dos campos semânticos e de governança no banco SQLite. - - Implementação do "Extrator de Contexto" hierárquico com cálculo de Hash. - - Implementação do motor Multi-Provider de IA com Structured Outputs (JSON) usando Pydantic. - - Comando: `gitauditor catalog summarize`. - -- **P3.2 | Auto-Tagging Híbrido:** - - Comando: `gitauditor catalog tag --auto`. - - Inferência primeiro determinística, depois refinada pela IA para categorizar projetos (`work`, `api`, `lab`, `archive`). - -- **P3.3 | Local Review (Code Quality):** - - Comando: `gitauditor repo review`. - - Foco restrito em analisar diffs curtos ou *staged* para apontar falhas qualitativas de design antes do commit. - -- **P3.4 | Rascunho de Documentação:** - - Comando: `gitauditor repo readme-draft`. - - Geração estruturada de READMEs a partir da stack detectada para reativar repositórios "mortos". - ---- -*Status: P0, P1, P2 Concluídos na Versão 3. Fase P3 em estágio de arquitetura e aprovação.* diff --git a/repomix-output.xml b/repomix-output.xml deleted file mode 100644 index ee5cdfb..0000000 --- a/repomix-output.xml +++ /dev/null @@ -1,113 +0,0 @@ -This file is a merged representation of the entire codebase, combined into a single document by Repomix. -The content has been processed where content has been compressed (code blocks are separated by ⋮---- delimiter). - - -This section contains a summary of this file. - - -This file contains a packed representation of the entire repository's contents. -It is designed to be easily consumable by AI systems for analysis, code review, -or other automated processes. - - - -The content is organized as follows: -1. This summary section -2. Repository information -3. Directory structure -4. Repository files (if enabled) -5. Multiple file entries, each consisting of: - - File path as an attribute - - Full contents of the file - - - -- This file should be treated as read-only. Any changes should be made to the - original repository files, not this packed version. -- When processing this file, use the file path to distinguish - between different files in the repository. -- Be aware that this file may contain sensitive information. Handle it with - the same level of security as you would the original repository. - - - -- Some files may have been excluded based on .gitignore rules and Repomix's configuration -- Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files -- Files matching patterns in .gitignore are excluded -- Files matching default ignore patterns are excluded -- Content has been compressed - code blocks are separated by ⋮---- delimiter -- Files are sorted by Git change count (files with more changes are at the bottom) - - - - - -.github/ - workflows/ - ci.yml -src/ - gitauditor/ - commands/ - __init__.py - amend_cmd.py - audit_cmd.py - catalog_cmd.py - changelog_cmd.py - config_cmd.py - policy_cmd.py - repo_app.py - repo_cmd.py - review_cmd.py - ssh_cmd.py - worktree_cmd.py - core/ - ai_api.py - audit_log.py - catalog.py - config.py - enricher.py - git_ops.py - heuristics.py - models.py - policy_engine.py - scanner.py - semantic.py - ssh_audit.py - locales/ - en_US/ - LC_MESSAGES/ - gitauditor.mo - gitauditor.po - messages.mo - messages.po - pt_BR/ - LC_MESSAGES/ - gitauditor.mo - gitauditor.po - messages.mo - messages.po - gitauditor.pot - __main__.py - cli.py - gitauditor.egg-info/ - dependency_links.txt - entry_points.txt - PKG-INFO - requires.txt - SOURCES.txt - top_level.txt -tests/ - test_enricher.py - test_point1_rebase_merges.py - test_point2_async_audit.py - test_point3_windows_rebase.py - test_point5_duplicates.py -.gitignore -BLUEPRINT.md -pyproject.toml -README_pt.md -README.md -requirements.txt -test_azure_key.py -test_azure.py - diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 5afc8d5..0000000 --- a/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -textual>=0.86.0 -GitPython>=3.1.43 -httpx>=0.27.0 -rich>=13.7.1 -pyyaml>=6.0.1 -tenacity>=8.0.0 -azure-identity>=1.15.0 From 766a4bad0cc281f578ad7d3470c541260f63686c Mon Sep 17 00:00:00 2001 From: Renan Fernandes Date: Sat, 27 Jun 2026 10:49:36 -0300 Subject: [PATCH 2/4] fix(cli): remove global state instantiation and mask exceptions --- src/gitauditor/cli.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/gitauditor/cli.py b/src/gitauditor/cli.py index 84b9f05..30c86a6 100644 --- a/src/gitauditor/cli.py +++ b/src/gitauditor/cli.py @@ -18,15 +18,18 @@ with open(config_path) as f: cfg = json.load(f) lang_to_use = cfg.get("lang", "pt_BR") -except Exception: - pass +except Exception as e: + import sys + print(f"Aviso: Erro ao carregar config i18n: {e}", file=sys.stderr) localedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'locales') try: translate = gettext.translation('gitauditor', localedir, languages=[lang_to_use], fallback=True) translate.install() _ = translate.gettext -except Exception: +except Exception as e: + import sys + print(f"Aviso: Não foi possível carregar a tradução: {e}", file=sys.stderr) import builtins builtins.__dict__['_'] = lambda x: x # ---------------------------------------------------- @@ -457,17 +460,24 @@ def _action_filter_table(self): app.add_typer(worktree_app, name="wt", hidden=True) app.command(name="config", help=_("Configurações do GitAuditor"))(config_command) -cli_state = GitAuditorCLI() + +_cli_state = None + +def get_cli_state() -> GitAuditorCLI: + global _cli_state + if _cli_state is None: + _cli_state = GitAuditorCLI() + return _cli_state @app.callback(invoke_without_command=True) def main_callback(ctx: typer.Context): if ctx.invoked_subcommand is None: - cli_state.run() + get_cli_state().run() @app.command() def ui(): """Modo Interativo (UI/Launcher Clássico).""" - cli_state.run() + get_cli_state().run() @app.command(name="sync", hidden=True) def sync_shortcut(): @@ -507,7 +517,7 @@ def review_shortcut(path: str = ".", staged: bool = False): @app.command(name="ssh", help=_("Gerenciar Chaves e Identidades SSH.")) def ssh_cmd(): - handle_manage_ssh(cli_state) + handle_manage_ssh(get_cli_state()) if __name__ == "__main__": From 912c03873f77e3fbb12f79b7d40fc43b87a200fa Mon Sep 17 00:00:00 2001 From: Renan Fernandes Date: Sat, 27 Jun 2026 10:54:45 -0300 Subject: [PATCH 3/4] test: rewrite cli tests with real command validation and mocking --- tests/test_cli_commands.py | 69 ++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 11 deletions(-) diff --git a/tests/test_cli_commands.py b/tests/test_cli_commands.py index c1bfcaf..1287049 100644 --- a/tests/test_cli_commands.py +++ b/tests/test_cli_commands.py @@ -1,3 +1,4 @@ +from unittest.mock import patch from typer.testing import CliRunner from gitauditor.cli import app @@ -8,14 +9,60 @@ def test_cli_help(): """Testa se o comando principal da CLI responde com o menu de ajuda.""" result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 - assert "Manage, audit, and organize Git repositories locally" in result.stdout or "Usage:" in result.stdout - -def test_catalog_dashboard_command_help(): - """Testa a ajuda do comando dashboard no catalog.""" - # Assuming there's a command structure or just testing the main help - result = runner.invoke(app, ["dashboard", "--help"]) - if result.exit_code == 0: - assert "dashboard" in result.stdout.lower() - else: - # Some commands might be invoked differently - pass + assert "GitAuditor" in result.stdout + assert "catalog" in result.stdout + assert "policy" in result.stdout + +def test_invalid_command_fails(): + """Testa se um comando inexistente falha corretamente.""" + result = runner.invoke(app, ["comando-inexistente"]) + assert result.exit_code != 0 + # Typer error messages might go to stderr or stdout depending on version, + # so we just check that it failed (exit_code != 0) + +@patch("gitauditor.commands.catalog_cmd.init_db") +@patch("gitauditor.commands.catalog_cmd.Session") +def test_catalog_sync_command(mock_session, mock_init_db): + """Testa o roteamento e a execução base do catalog sync, com mock do banco de dados.""" + # Como não temos um DB de teste configurado, vamos injetar um erro simulado no session + # ou deixar passar se o script só instanciar. + result = runner.invoke(app, ["catalog", "sync", "--help"]) + assert result.exit_code == 0 + assert "Sincroniza" in result.stdout or "sync" in result.stdout + +@patch("gitauditor.commands.policy_cmd.PolicyEngine") +@patch("gitauditor.commands.policy_cmd.find_repo_or_exit") +def test_policy_check_command(mock_find, mock_engine): + """Testa se o comando policy check é chamado adequadamente.""" + # Mock find_repo to return a dummy path + mock_find.return_value = "/tmp/dummy/repo" + # Mock the check_repository method to return a dummy result + mock_engine.check_repository.return_value = { + "status": "ok", + "score": 100, + "critical": [], + "warnings": [], + "checks": { + "readme": True, "license": True, "gitignore": True, + "ci_cd": True, "codeowners": True, "contributing": True, + "security": True, "env_exposed": False + } + } + + result = runner.invoke(app, ["policy", "check", "."]) + assert result.exit_code == 0 + # Verifica se a saída indica que a política foi avaliada (o código exato depende do rich print, + # mas o exit_code == 0 já indica que não quebrou por Exception). + +def test_repo_amend_help(): + """Testa o help do comando de inteligência artificial de repositório.""" + result = runner.invoke(app, ["repo", "amend", "--help"]) + assert result.exit_code == 0 + assert "amend" in result.stdout.lower() + +@patch("gitauditor.commands.catalog_cmd.health_dashboard") +def test_hidden_health_shortcut(mock_health): + """Testa se os atalhos escondidos do CLI estão roteando corretamente.""" + result = runner.invoke(app, ["health"]) + assert result.exit_code == 0 + mock_health.assert_called_once() From f0525f26fdbe408074162884989b9ac766f13786 Mon Sep 17 00:00:00 2001 From: Renan Sousa <100616022+refernandes@users.noreply.github.com> Date: Sat, 27 Jun 2026 11:00:43 -0300 Subject: [PATCH 4/4] fix: corrige I001 (import order) e W291/W293 (trailing whitespace) em test_cli_commands.py --- tests/test_cli_commands.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/tests/test_cli_commands.py b/tests/test_cli_commands.py index 1287049..07ef61d 100644 --- a/tests/test_cli_commands.py +++ b/tests/test_cli_commands.py @@ -1,10 +1,11 @@ -from unittest.mock import patch from typer.testing import CliRunner +from unittest.mock import patch from gitauditor.cli import app runner = CliRunner() + def test_cli_help(): """Testa se o comando principal da CLI responde com o menu de ajuda.""" result = runner.invoke(app, ["--help"]) @@ -13,46 +14,48 @@ def test_cli_help(): assert "catalog" in result.stdout assert "policy" in result.stdout + def test_invalid_command_fails(): """Testa se um comando inexistente falha corretamente.""" result = runner.invoke(app, ["comando-inexistente"]) assert result.exit_code != 0 - # Typer error messages might go to stderr or stdout depending on version, - # so we just check that it failed (exit_code != 0) + @patch("gitauditor.commands.catalog_cmd.init_db") @patch("gitauditor.commands.catalog_cmd.Session") def test_catalog_sync_command(mock_session, mock_init_db): """Testa o roteamento e a execução base do catalog sync, com mock do banco de dados.""" - # Como não temos um DB de teste configurado, vamos injetar um erro simulado no session - # ou deixar passar se o script só instanciar. + # Como não temos um DB de teste configurado, verificamos apenas o help do subcomando. result = runner.invoke(app, ["catalog", "sync", "--help"]) assert result.exit_code == 0 assert "Sincroniza" in result.stdout or "sync" in result.stdout + @patch("gitauditor.commands.policy_cmd.PolicyEngine") @patch("gitauditor.commands.policy_cmd.find_repo_or_exit") def test_policy_check_command(mock_find, mock_engine): """Testa se o comando policy check é chamado adequadamente.""" - # Mock find_repo to return a dummy path mock_find.return_value = "/tmp/dummy/repo" - # Mock the check_repository method to return a dummy result - mock_engine.check_repository.return_value = { - "status": "ok", + mock_engine.return_value.check_repository.return_value = { + "status": "ok", "score": 100, "critical": [], "warnings": [], "checks": { - "readme": True, "license": True, "gitignore": True, - "ci_cd": True, "codeowners": True, "contributing": True, - "security": True, "env_exposed": False - } + "readme": True, + "license": True, + "gitignore": True, + "ci_cd": True, + "codeowners": True, + "contributing": True, + "security": True, + "env_exposed": False, + }, } - + result = runner.invoke(app, ["policy", "check", "."]) assert result.exit_code == 0 - # Verifica se a saída indica que a política foi avaliada (o código exato depende do rich print, - # mas o exit_code == 0 já indica que não quebrou por Exception). + def test_repo_amend_help(): """Testa o help do comando de inteligência artificial de repositório.""" @@ -60,6 +63,7 @@ def test_repo_amend_help(): assert result.exit_code == 0 assert "amend" in result.stdout.lower() + @patch("gitauditor.commands.catalog_cmd.health_dashboard") def test_hidden_health_shortcut(mock_health): """Testa se os atalhos escondidos do CLI estão roteando corretamente."""