From f875d9902896858ccf2ef1580448125dda647168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Ren=C3=A3?= Date: Tue, 7 Oct 2025 17:19:41 -0300 Subject: [PATCH 01/36] Adicionando um novo agente e sua respectiva task --- codewise_lib/config/agents.yaml | 4 ++++ codewise_lib/config/tasks.yaml | 7 +++++++ codewise_lib/crew.py | 13 ++++++++++--- codewise_lib/cw_runner.py | 16 ++++++++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/codewise_lib/config/agents.yaml b/codewise_lib/config/agents.yaml index 6f97f6e..fb5ddf2 100644 --- a/codewise_lib/config/agents.yaml +++ b/codewise_lib/config/agents.yaml @@ -25,3 +25,7 @@ summary_specialist: goal: Gerar um resumo técnico claro e objetivo do que foi implementado ou modificado no commit analisado. backstory: Um engenheiro de software experiente em revisão de código e documentação, responsável por redigir resumos de alterações para pull requests sem julgamentos ou sugestões. +code_mentor: + role: Mentor de Sugestões Educacionais + goal: Identificar oportunidades de aprendizado baseadas nas análises técnicas e sugerir artigos, cursos, ou videos para ajudar a desenvolver habilidades de desenvolvedor. + backstory: Um educador experiente em engenharia de software com vasto conhecimento em recursos de aprendizado, cursos online e materiais educativos. Especialista em Identificar dificuldades e transformar críticas técnicas em oportunidades de crescimento profissional. \ No newline at end of file diff --git a/codewise_lib/config/tasks.yaml b/codewise_lib/config/tasks.yaml index 1b4ec7c..8de4f50 100644 --- a/codewise_lib/config/tasks.yaml +++ b/codewise_lib/config/tasks.yaml @@ -38,3 +38,10 @@ summarize_analysis: expected_output: > Um parágrafo bem estruturado com as alterações realizadas, pronto para ser usado como descrição de um Pull Request em (pt-br). +mentoring_task: + description: > + Com base nas análises técnicas realizadas, + identifique oportunidades de aprendizado e sugira recursos educacionais personalizados para ajudar o desenvolvedor a melhorar suas habilidades técnicas. + expected_output: > + Documento sugestoes_aprendizado.md com recursos educacionais específicos (artigos, cursos, vídeos) organizados por tópico e conectados aos problemas identificados. + agent: code_mentor \ No newline at end of file diff --git a/codewise_lib/crew.py b/codewise_lib/crew.py index a9c0083..7272bcd 100644 --- a/codewise_lib/crew.py +++ b/codewise_lib/crew.py @@ -48,6 +48,8 @@ def quality_consultant(self) -> Agent: return Agent(config=self.agents_config['q def quality_control_manager(self) -> Agent: return Agent(config=self.agents_config['quality_control_manager'], llm=self.llm, verbose=False) @agent def summary_specialist(self) -> Agent: return Agent(config=self.agents_config['summary_specialist'], llm=self.llm, verbose=False) + @agent + def code_mentor(self) -> Agent: return Agent(config=self.agents_config['code_mentor'], llm=self.llm, verbose=False) @task def task_estrutura(self) -> Task: @@ -69,15 +71,20 @@ def task_padroes(self) -> Task: def task_summarize(self) -> Task: cfg = self.tasks_config['summarize_analysis'] return Task(description=cfg['description'], expected_output=cfg['expected_output'], agent=self.summary_specialist()) + + @task + def task_mentoring(self) -> Task: + cfg = self.tasks_config['mentoring_task'] + return Task(description=cfg['description'], expected_output=cfg['expected_output'], agent=self.code_mentor()) @crew def crew(self) -> Crew: return Crew( - agents=[self.senior_architect(), self.senior_analytics(), self.quality_consultant(), self.quality_control_manager()], - tasks=[self.task_estrutura(), self.task_heuristicas(), self.task_solid(), self.task_padroes()], + agents=[self.senior_architect(), self.senior_analytics(), self.quality_consultant(), self.quality_control_manager(),self.code_mentor()], + tasks=[self.task_estrutura(), self.task_heuristicas(), self.task_solid(), self.task_padroes(),self.task_mentoring()], process=Process.sequential ) - + def summary_crew(self) -> Crew: return Crew( agents=[self.summary_specialist()], diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index 68b5cd0..5f25791 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -83,6 +83,22 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): ) resultado_final = Crew(agents=[resumo_agent], tasks=[resumo_task]).kickoff() + mentor_agent = codewise_instance.code_mentor() + mentor_task = Task( + description="Com base nas análises técnicas realizadas, comente e sugira recursos educacionais personalizados com base nas mudanças **obrigatoriamente em Português do Brasil**, bem formatado em markdown, com links que possuam conteúdo para melhorar o código.", + expected_output="Sugestões de melhoria.", + agent=mentor_agent, + context=analysis_crew.tasks + ) + resultado_mentor = Crew(agents=[mentor_agent],tasks=[mentor_task]).kickoff() + mentor_file_path = os.path.join(output_dir_path, "sugestoes_aprendizado.md") + try: + with open(mentor_file_path, "w", encoding="utf-8") as f: + f.write(str(resultado_mentor)) + print(f" - Arquivo 'sugestoes_aprendizado.md' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) + except Exception as e: + print(f" - ERRO ao salvar o arquivo 'sugestoes_aprendizado.md': {e}", file=sys.stderr) + elif modo == 'lint': agent = codewise_instance.quality_consultant() task = Task(description=f"Analise rapidamente as seguintes mudanças de código ('git diff') e aponte APENAS problemas óbvios ou code smells. A resposta deve ser **obrigatoriamente em Português do Brasil**. Seja conciso. Se não houver problemas, retorne 'Nenhum problema aparente detectado.'.\n\nCódigo a ser analisado:\n{contexto_para_ia}", expected_output="Uma lista curta em bullet points com sugestões, ou uma mensagem de que está tudo ok.", agent=agent) From e7c4e47276b315d82ff788f584ea156f1cefae03 Mon Sep 17 00:00:00 2001 From: Gustavo Saraiva Date: Mon, 27 Oct 2025 17:08:32 -0300 Subject: [PATCH 02/36] =?UTF-8?q?flexibiliza=C3=A7=C3=A3o=20da=20escolha?= =?UTF-8?q?=20de=20llm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codewise_lib/create_llm.py | 67 ++++++++++++++++++++++++++++++++++++++ codewise_lib/crew.py | 14 ++------ requirements.txt | 3 +- 3 files changed, 72 insertions(+), 12 deletions(-) create mode 100644 codewise_lib/create_llm.py diff --git a/codewise_lib/create_llm.py b/codewise_lib/create_llm.py new file mode 100644 index 0000000..9807c7b --- /dev/null +++ b/codewise_lib/create_llm.py @@ -0,0 +1,67 @@ +import os +import sys +from dotenv import load_dotenv + +#import dos modelos de api que o codewise vai suportar +try: + from langchain_google_genai import ChatGoogleGenerativeAI + from langchain_openai import ChatOpenAI + +# trata erros de importação caso as libs não estejam instaladas +# instalar: pip install -r requirements.txt (adicionar as outras llms no requirements.txt) +except ImportError: + print("Erro: Bibliotecas de LLM (ex: langchain-google-genai, langchain-openai) não encontradas.", file=sys.stderr) + print("Instale as dependências necessárias (verifique o requirements.txt).", file=sys.stderr) + sys.exit(1) + +def create_llm(): + """ + Lê as variáveis de ambiente e instancia o modelo de LLM selecionado. + """ + load_dotenv() + + provider = os.getenv("AI_PROVIDER", "google").lower() # só de garantia, se não for definido, setei a do goodle como padrão + # o modelo acho que é válido ser opcional, pro usuário escolher o modelo padrão do provider se quiser + # acho que resolve aquela questão de querer o gemini pro, fica a critério do usuário + model_name = os.getenv("AI_MODEL") + + print(f"--- 🤖 Inicializando IA com o provedor: {provider} ---", file=sys.stderr) + + try: + if provider == "google": + api_key = os.getenv("GEMINI_API_KEY") + if not api_key: + print("Erro: AI_PROVIDER='google', mas GEMINI_API_KEY não foi definida no .env", file=sys.stderr) + sys.exit(1) + + model = model_name or "gemini-2.0-flash" # se não for definido, usa a versão gratuita como padrão + print(f"Usando Google (Gemini) - Modelo: {model}", file=sys.stderr) + return ChatGoogleGenerativeAI( + model_name=model, + google_api_key=api_key + ) + + elif provider == "openai": + api_key = os.getenv("OPENAI_API_KEY") + if not api_key: + print("Erro: AI_PROVIDER='openai', mas OPENAI_API_KEY não foi definida no .env", file=sys.stderr) + sys.exit(1) + + model = model_name or "gpt-4o-mini" #mesma coisa, usa esse modelo como padrão se não for definido um específicosw + print(f"Usando OpenAI - Modelo: {model}", file=sys.stderr) + return ChatOpenAI( + model_name=model, + api_key=api_key + ) + + # se quisermos, da pra adicionar outras llms aqui + + else: + print(f"Erro: AI_PROVIDER '{provider}' não é suportado. (Use 'google' ou 'openai')", file=sys.stderr) + sys.exit(1) + + except Exception as e: + print(f"Erro ao inicializar o LLM para o provider '{provider}'.", file=sys.stderr) + print(f"Verifique suas chaves de API e se o modelo '{model_name}' é válido.", file=sys.stderr) + print(f"Erro original: {e}") + sys.exit(1) \ No newline at end of file diff --git a/codewise_lib/crew.py b/codewise_lib/crew.py index 7272bcd..54cfd84 100644 --- a/codewise_lib/crew.py +++ b/codewise_lib/crew.py @@ -5,6 +5,7 @@ from dotenv import load_dotenv from crewai import Agent, Crew, Process, Task, LLM from crewai.project import CrewBase, agent, crew, task +from codewise_lib.create_llm import create_llm @CrewBase class Codewise: @@ -12,17 +13,8 @@ class Codewise: def __init__(self, commit_message: str = ""): load_dotenv() self.commit_message = commit_message - if not os.getenv("GEMINI_API_KEY"): - print("Erro: A variável de ambiente GEMINI_API_KEY não foi definida.") - sys.exit(1) - try: - self.llm = LLM( - model="gemini/gemini-2.0-flash", - temperature=0.7 - ) - except Exception as e: - print(f"Erro ao inicializar o LLM. Verifique sua chave de API e dependências. Erro: {e}") - sys.exit(1) + + self.llm = create_llm() # chama a criação da llm que o usuário tiver escolhido dno .env base_dir = os.path.dirname(os.path.abspath(__file__)) config_path = os.path.join(base_dir, "config") diff --git a/requirements.txt b/requirements.txt index 23565c2..30dcc07 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ crewai python-dotenv pyyaml google-generativeai -langchain-google-genai \ No newline at end of file +langchain-google-genai +langchain-openai \ No newline at end of file From 72aa32d435290bdba76b20ebe8f758696f53471d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Ren=C3=A3?= Date: Tue, 28 Oct 2025 22:19:48 -0300 Subject: [PATCH 03/36] tentando flexibilizar api keys --- codewise_lib/crew.py | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/codewise_lib/crew.py b/codewise_lib/crew.py index 7272bcd..3e47fd9 100644 --- a/codewise_lib/crew.py +++ b/codewise_lib/crew.py @@ -12,18 +12,32 @@ class Codewise: def __init__(self, commit_message: str = ""): load_dotenv() self.commit_message = commit_message - if not os.getenv("GEMINI_API_KEY"): - print("Erro: A variável de ambiente GEMINI_API_KEY não foi definida.") - sys.exit(1) - try: - self.llm = LLM( - model="gemini/gemini-2.0-flash", - temperature=0.7 - ) - except Exception as e: - print(f"Erro ao inicializar o LLM. Verifique sua chave de API e dependências. Erro: {e}") - sys.exit(1) - + provider = os.getenv("AI_PROVIDER","gemini").upper() + + if provider == "GEMINI": + if not os.getenv("GEMINI_API_KEY"): + print("Erro: A variável de ambiente GEMINI_API_KEY não foi definida.") + sys.exit(1) + try: + self.llm = LLM( + model= os.getenv("AI_MODEL"), + temperature=0.7 + ) + except Exception as e: + print(f"Erro ao inicializar o LLM. Verifique sua chave de API e dependências. Erro: {e}") + sys.exit(1) + elif provider == "OPENAI": + if not os.getenv("OPENAI_API_KEY"): + print("Erro: A variável de ambiente OPENAI_API_KEY não foi definida.") + sys.exit(1) + try: + self.llm = LLM( + model= os.getenv("AI_MODEL"), + temperature=0.8, + ) + except Exception as e: + print(f"Erro ao inicializar o LLM. Verifique sua chave de API e dependências. Erro: {e}") + sys.exit(1) base_dir = os.path.dirname(os.path.abspath(__file__)) config_path = os.path.join(base_dir, "config") agents_path = os.path.join(config_path, "agents.yaml") From 48731b20722ce3e65886e46f1dbe7100fb439b4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Ren=C3=A3?= Date: Tue, 28 Oct 2025 22:26:12 -0300 Subject: [PATCH 04/36] padronizando caminho da api --- codewise_lib/crew.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codewise_lib/crew.py b/codewise_lib/crew.py index 3e47fd9..5d60696 100644 --- a/codewise_lib/crew.py +++ b/codewise_lib/crew.py @@ -20,7 +20,7 @@ def __init__(self, commit_message: str = ""): sys.exit(1) try: self.llm = LLM( - model= os.getenv("AI_MODEL"), + model= "gemini/" + os.getenv("AI_MODEL"), temperature=0.7 ) except Exception as e: @@ -32,7 +32,7 @@ def __init__(self, commit_message: str = ""): sys.exit(1) try: self.llm = LLM( - model= os.getenv("AI_MODEL"), + model= "openai/" + os.getenv("AI_MODEL"), temperature=0.8, ) except Exception as e: From cc49e2481d7d44169150f1be903dc547a3c1f3fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Ren=C3=A3?= Date: Wed, 29 Oct 2025 18:02:56 -0300 Subject: [PATCH 05/36] adicionando arquivo para selecionar llm e atualizando crew --- codewise_lib/crew.py | 31 +++++----------------------- codewise_lib/select_llm.py | 41 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 26 deletions(-) create mode 100644 codewise_lib/select_llm.py diff --git a/codewise_lib/crew.py b/codewise_lib/crew.py index 5d60696..8ccf18d 100644 --- a/codewise_lib/crew.py +++ b/codewise_lib/crew.py @@ -5,6 +5,7 @@ from dotenv import load_dotenv from crewai import Agent, Crew, Process, Task, LLM from crewai.project import CrewBase, agent, crew, task +from .select_llm import create_llm @CrewBase class Codewise: @@ -12,32 +13,10 @@ class Codewise: def __init__(self, commit_message: str = ""): load_dotenv() self.commit_message = commit_message - provider = os.getenv("AI_PROVIDER","gemini").upper() - - if provider == "GEMINI": - if not os.getenv("GEMINI_API_KEY"): - print("Erro: A variável de ambiente GEMINI_API_KEY não foi definida.") - sys.exit(1) - try: - self.llm = LLM( - model= "gemini/" + os.getenv("AI_MODEL"), - temperature=0.7 - ) - except Exception as e: - print(f"Erro ao inicializar o LLM. Verifique sua chave de API e dependências. Erro: {e}") - sys.exit(1) - elif provider == "OPENAI": - if not os.getenv("OPENAI_API_KEY"): - print("Erro: A variável de ambiente OPENAI_API_KEY não foi definida.") - sys.exit(1) - try: - self.llm = LLM( - model= "openai/" + os.getenv("AI_MODEL"), - temperature=0.8, - ) - except Exception as e: - print(f"Erro ao inicializar o LLM. Verifique sua chave de API e dependências. Erro: {e}") - sys.exit(1) + provider = os.getenv("AI_PROVIDER").upper() + model = os.getenv("AI_MODEL") + self.llm = create_llm(provider,model) + base_dir = os.path.dirname(os.path.abspath(__file__)) config_path = os.path.join(base_dir, "config") agents_path = os.path.join(config_path, "agents.yaml") diff --git a/codewise_lib/select_llm.py b/codewise_lib/select_llm.py new file mode 100644 index 0000000..8c9921b --- /dev/null +++ b/codewise_lib/select_llm.py @@ -0,0 +1,41 @@ +import os +from crewai import LLM +import sys + +def create_llm(provider:str, model:str)-> LLM: + if provider == "GEMINI": + if not os.getenv("GEMINI_API_KEY"): + print("Erro: A variável de ambiente GEMINI_API_KEY não foi definida.") + sys.exit(1) + try: + return LLM( + model= "gemini/" + model, + temperature=0.7 + ) + except Exception as e: + print(f"Erro ao inicializar o LLM. Verifique sua chave de API e dependências. Erro: {e}") + sys.exit(1) + elif provider == "OPENAI": + if not os.getenv("OPENAI_API_KEY"): + print("Erro: A variável de ambiente OPENAI_API_KEY não foi definida.") + sys.exit(1) + try: + return LLM( + model= "openai/" + model, + temperature=0.7, + ) + except Exception as e: + print(f"Erro ao inicializar o LLM. Verifique sua chave de API e dependências. Erro: {e}") + sys.exit(1) + elif provider == "GROQ": + if not os.getenv("GROQ_API_KEY"): + print("Erro: A variável de ambiente OPENAI_API_KEY não foi definida.") + sys.exit(1) + try: + return LLM( + model= "groq/" + model, + temperature=0.7, + ) + except Exception as e: + print(f"Erro ao inicializar o LLM. Verifique sua chave de API e dependências. Erro: {e}") + sys.exit(1) \ No newline at end of file From 98ae061e1c4d60913ca9bb5b2457d56ef0523a31 Mon Sep 17 00:00:00 2001 From: Matheus Magnago Date: Sat, 1 Nov 2025 19:05:27 -0300 Subject: [PATCH 06/36] implementation of the policy_analytics task and agent --- codewise_lib/config/agents.yaml | 12 +++++++++++- codewise_lib/config/tasks.yaml | 18 +++++++++++++++++- codewise_lib/crew.py | 30 +++++++++++++++++++++++++++--- codewise_lib/cw_runner.py | 24 +++++++++++++++++++++++- 4 files changed, 78 insertions(+), 6 deletions(-) diff --git a/codewise_lib/config/agents.yaml b/codewise_lib/config/agents.yaml index fb5ddf2..d9be0c7 100644 --- a/codewise_lib/config/agents.yaml +++ b/codewise_lib/config/agents.yaml @@ -28,4 +28,14 @@ summary_specialist: code_mentor: role: Mentor de Sugestões Educacionais goal: Identificar oportunidades de aprendizado baseadas nas análises técnicas e sugerir artigos, cursos, ou videos para ajudar a desenvolver habilidades de desenvolvedor. - backstory: Um educador experiente em engenharia de software com vasto conhecimento em recursos de aprendizado, cursos online e materiais educativos. Especialista em Identificar dificuldades e transformar críticas técnicas em oportunidades de crescimento profissional. \ No newline at end of file + backstory: Um educador experiente em engenharia de software com vasto conhecimento em recursos de aprendizado, cursos online e materiais educativos. Especialista em Identificar dificuldades e transformar críticas técnicas em oportunidades de crescimento profissional. + +dataCollect_policy_analytics: + role: Especialista em análise de políticas de coletas de dados + goal: Analisar documentações sobre a política de coleta de dados de modelos de api keys fornecido por um provedor para uma futura comparação com as leis LGPD. + backstory: Analista de segurança de dados com foco na política de coleta de dados de um modelo de api key fornecido por um determinado provedor. + +lgpd_judge: + role: Especialista em leis LGPD + goal: Comparar o resumo da política de coleta de dados de um provedor de api key com as leis LGPD e determinar se tal provedor está respeitando as leis LGPD. + backstory: Um especialista em leis LGPD que julga se há o cumprimento dessas leis por parte do provedor da api key. \ No newline at end of file diff --git a/codewise_lib/config/tasks.yaml b/codewise_lib/config/tasks.yaml index 8de4f50..35721eb 100644 --- a/codewise_lib/config/tasks.yaml +++ b/codewise_lib/config/tasks.yaml @@ -44,4 +44,20 @@ mentoring_task: identifique oportunidades de aprendizado e sugira recursos educacionais personalizados para ajudar o desenvolvedor a melhorar suas habilidades técnicas. expected_output: > Documento sugestoes_aprendizado.md com recursos educacionais específicos (artigos, cursos, vídeos) organizados por tópico e conectados aos problemas identificados. - agent: code_mentor \ No newline at end of file + agent: code_mentor + +policy_analytics: + description: > + Crie um relatório sobre a política de coleta de dados do "{IA_PROVIDER}" para o modelo "{IA_MODEL}" + com base na documentação oficial. + expected_output: > + Relatório data_collect_policy.md com um resumo dos principais pontos sobre a política de coleta de dados do provedor. + agent: dataCollect_policy_analytics + +lgpd_judging: + description: > + Com base nas leis LGPD e no relatório da analise da documentação, + determine se o provedor da api key está respeitando ou não as leis LGPD. + expected_output: > + Documento status_lgpd.md com as provas ou artigos utilizados para determinar a decisão tomada. + agent: lgpd_judge \ No newline at end of file diff --git a/codewise_lib/crew.py b/codewise_lib/crew.py index 8ccf18d..4abc4e2 100644 --- a/codewise_lib/crew.py +++ b/codewise_lib/crew.py @@ -13,8 +13,10 @@ class Codewise: def __init__(self, commit_message: str = ""): load_dotenv() self.commit_message = commit_message - provider = os.getenv("AI_PROVIDER").upper() - model = os.getenv("AI_MODEL") + # self para utilizar na task de analise da politica + self.provider = os.getenv("AI_PROVIDER").upper() + self.model = os.getenv("AI_MODEL") + ## self.llm = create_llm(provider,model) base_dir = os.path.dirname(os.path.abspath(__file__)) @@ -43,6 +45,12 @@ def quality_control_manager(self) -> Agent: return Agent(config=self.agents_conf def summary_specialist(self) -> Agent: return Agent(config=self.agents_config['summary_specialist'], llm=self.llm, verbose=False) @agent def code_mentor(self) -> Agent: return Agent(config=self.agents_config['code_mentor'], llm=self.llm, verbose=False) + + @agent + def dataCollect_policy_analytics(self) -> Agent: return Agent(config=self.agents_config['dataCollect_policy_analytics'], llm=self.llm, verbose=False) + + @agent + def lgpd_judge(self) -> Agent: return Agent(config=self.agents_config['lgpd_judge'], llm=self.llm, verbose = False) @task def task_estrutura(self) -> Task: @@ -69,12 +77,28 @@ def task_summarize(self) -> Task: def task_mentoring(self) -> Task: cfg = self.tasks_config['mentoring_task'] return Task(description=cfg['description'], expected_output=cfg['expected_output'], agent=self.code_mentor()) + + @task + def task_policy(self) -> Task: + cfg = self.tasks_config['policy_analytics'] + + formatted_description = cfg['description'].format( + IA_PROVIDER=self.provider, + IA_MODEL=self.model + ) + + return Task(description=formatted_description, expected_output=cfg['expected_output'], agent=self.dataCollect_policy_analytics()) + + @task + def task_judging(self) -> Task: + cfg = self.tasks_config['lgpd_judging'] + return Task(description=cfg['description'], expected_output=cfg['expected_output'], agent=self.task_policy_analytics()) @crew def crew(self) -> Crew: return Crew( agents=[self.senior_architect(), self.senior_analytics(), self.quality_consultant(), self.quality_control_manager(),self.code_mentor()], - tasks=[self.task_estrutura(), self.task_heuristicas(), self.task_solid(), self.task_padroes(),self.task_mentoring()], + tasks=[self.task_estrutura(), self.task_heuristicas(), self.task_solid(), self.task_padroes(),self.task_mentoring(), self.task_judging()], process=Process.sequential ) diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index 5f25791..204d1b2 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -10,8 +10,9 @@ def __init__(self): self.caminho_entrada = os.path.join(self.BASE_DIR, ".entrada_temp.txt") def executar(self, caminho_repo: str, nome_branch: str, modo: str): - contexto_para_ia = "" + contexto_para_ia = "" + if modo == 'lint': resultado_git = obter_mudancas_staged(caminho_repo) @@ -98,6 +99,27 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): print(f" - Arquivo 'sugestoes_aprendizado.md' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) except Exception as e: print(f" - ERRO ao salvar o arquivo 'sugestoes_aprendizado.md': {e}", file=sys.stderr) + + + # Tentativa de colocar a analise lgpd para rodar antes do envio dos dados sensiveis + policy_agent = codewise_instance.dataCollect_policy_analytics() + provedor_usado = codewise_instance.provider + modelo_api_key = codewise_instance.model + + policy_analytics_task = Task( + description=f"Analise a documentação oficial da política de coleta de dados do provedor '{provedor_usado}' para o modelo '{modelo_api_key}'. Crie um relatório, **obrigatoriamente em Português do Brasil**, focado nos pontos-chave de como os dados de entrada do usuário (inputs de API) são tratados, incluindo coleta, uso e retenção.", + expected_output="Um relatório sobre a política de coleta de dados do modelo de api utilizado", + agent=policy_analytics_agent + ) + resultado_policy = Crew(agents=[policy_analytics_agent], tasks=[policy_analytics_task]).kickoff() + policy_file_path = os.path.join(output_dir_path, "analise_politica_coleta_de_dados.md") + try: + with open(policy_file_path, "w", encoding="utf-8") as f: + f.write(str(resultado_policy)) + print(f" - Arquivo 'analise_politica_coleta_de_dados.md' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) + except Exception as e: + print(f" - ERRO ao salvar o arquivo 'analise_politica_coleta_de_dados.md': {e}", file=sys.stderr) + # fim elif modo == 'lint': agent = codewise_instance.quality_consultant() From c068f048a6eee8c9b582ffe55cccf152f3f68126 Mon Sep 17 00:00:00 2001 From: Matheus Magnago Date: Sun, 2 Nov 2025 09:23:12 -0300 Subject: [PATCH 07/36] Criacao de um time para analises e julgamentos LGPD e arquivos justificando os resultados das analises e julgamentos LGPD. --- codewise_lib/config/tasks.yaml | 2 +- codewise_lib/crew.py | 14 +- codewise_lib/cw_runner.py | 271 +++++++++++++++++++-------------- 3 files changed, 171 insertions(+), 116 deletions(-) diff --git a/codewise_lib/config/tasks.yaml b/codewise_lib/config/tasks.yaml index 35721eb..7b98682 100644 --- a/codewise_lib/config/tasks.yaml +++ b/codewise_lib/config/tasks.yaml @@ -57,7 +57,7 @@ policy_analytics: lgpd_judging: description: > Com base nas leis LGPD e no relatório da analise da documentação, - determine se o provedor da api key está respeitando ou não as leis LGPD. + determine se o provedor da api key está respeitando ou não as leis LGPD e justifique o julgamento apontando as provas, por meio de um relatório, onde a primeira linha do arquivo é a resposta antecipada (sim ou não). expected_output: > Documento status_lgpd.md com as provas ou artigos utilizados para determinar a decisão tomada. agent: lgpd_judge \ No newline at end of file diff --git a/codewise_lib/crew.py b/codewise_lib/crew.py index 4abc4e2..b9db30b 100644 --- a/codewise_lib/crew.py +++ b/codewise_lib/crew.py @@ -94,17 +94,27 @@ def task_judging(self) -> Task: cfg = self.tasks_config['lgpd_judging'] return Task(description=cfg['description'], expected_output=cfg['expected_output'], agent=self.task_policy_analytics()) + @crew def crew(self) -> Crew: return Crew( agents=[self.senior_architect(), self.senior_analytics(), self.quality_consultant(), self.quality_control_manager(),self.code_mentor()], - tasks=[self.task_estrutura(), self.task_heuristicas(), self.task_solid(), self.task_padroes(),self.task_mentoring(), self.task_judging()], + tasks=[self.task_estrutura(), self.task_heuristicas(), self.task_solid(), self.task_padroes(),self.task_mentoring()], process=Process.sequential ) - + + @crew def summary_crew(self) -> Crew: return Crew( agents=[self.summary_specialist()], tasks=[self.task_summarize()], process=Process.sequential + ) + + @crew + def lgpd_crew(self) -> Crew: + return Crew( + agents=[self.dataCollect_policy_analytics(), self.lgpd_judge()], + tasks=[self.task_policy(), self.task_judging()], + process=Process.sequential ) \ No newline at end of file diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index 204d1b2..dfffa81 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -11,125 +11,170 @@ def __init__(self): def executar(self, caminho_repo: str, nome_branch: str, modo: str): - contexto_para_ia = "" + # Tentativa de colocar a analise lgpd para rodar antes do envio dos dados sensiveis + # instancia sem passar o commit como contexto + codewise_instance = Codewise() - if modo == 'lint': - resultado_git = obter_mudancas_staged(caminho_repo) - - if resultado_git is None: - print("Nenhum problema aparente detectado.") - sys.exit(0) - - if resultado_git.startswith("AVISO:") or resultado_git.startswith("FALHA:"): - print(resultado_git) - sys.exit(0) - - contexto_para_ia = resultado_git - else: - if not gerar_entrada_automatica(caminho_repo, self.caminho_entrada, nome_branch): - sys.exit(0) - contexto_para_ia = self._ler_arquivo(self.caminho_entrada) + print(f"Verificando a política de coleta de dados do provedor com base neste modelo de api key...") + + #lgpd crew + lgpd_check = codewise_instance.lgpd_crew() + + # roda a analise e o julgamento lgpd (todo o time) + lgpd_check.kickoff() + + #definicao do caminho + output_dir_name = "analises-julgamento-lgpd" + output_dir_path = os.path.join(caminho_repo, output_dir_name) + os.makedirs(output_dir_path, exist_ok=True) + + # salvando o resultado da analise da politica de coleta de dados + resultado_policy = lgpd_check.tasks[0].output + + # definindo caminho e nome do arquivo final. + policy_file_path = os.path.join(output_dir_path, "analise_politica_coleta_de_dados.md") + + try: + with open(policy_file_path, "w", encoding="utf-8") as f: + f.write(str(resultado_policy)) + print(f" - Arquivo 'analise_politica_coleta_de_dados.md' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) + except Exception as e: + print(f" - ERRO ao salvar o arquivo 'analise_politica_coleta_de_dados.md': {e}", file=sys.stderr) - codewise_instance = Codewise(commit_message=contexto_para_ia) - resultado_final = "" - - if modo == 'titulo': - agent = codewise_instance.summary_specialist() - task = Task(description=f"Crie um título de PR conciso no padrão Conventional Commits para as seguintes mudanças. A resposta deve ser APENAS o título, **obrigatoriamente em Português do Brasil**, sem aspas, acentos graves ou qualquer outro texto:\n{contexto_para_ia}", expected_output="Um único título de PR.", agent=agent) - resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() - - elif modo == 'descricao': - agent = codewise_instance.summary_specialist() - task = Task(description=f"Crie uma descrição de um parágrafo **obrigatoriamente em Português do Brasil** para um Pull Request para as seguintes mudanças:\n{contexto_para_ia}", expected_output="Um único parágrafo de texto.", agent=agent) - resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() - - elif modo == 'analise': - analysis_crew = codewise_instance.crew() - analysis_crew.kickoff(inputs={'input': contexto_para_ia}) - - print("Salvando relatórios de análise individuais...", file=sys.stderr) - - output_dir_name = "analises-concluidas" - output_dir_path = os.path.join(caminho_repo, output_dir_name) - os.makedirs(output_dir_path, exist_ok=True) - - keyword_map = { - "inspeção na estrutura do projeto": "arquitetura_atual.md", - "analise as integrações, bibliotecas externas e APIs": "analise_heuristicas_integracoes.md", - "aderência da mudança aos princípios S.O.L.I.D.": "analise_solid.md", - "aplicação correta ou ausência de padrões de projeto": "padroes_de_projeto.md" - } - - tasks_processed = {key: False for key in keyword_map} - - for task in analysis_crew.tasks: - for keyword, filename in keyword_map.items(): - if keyword in task.description and not tasks_processed[keyword]: - file_path = os.path.join(output_dir_path, filename) - try: - with open(file_path, "w", encoding="utf-8") as f: - f.write(str(task.output)) - print(f" - Arquivo '{filename}' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) - tasks_processed[keyword] = True - break - except Exception as e: - print(f" - ERRO ao salvar o arquivo '{filename}': {e}", file=sys.stderr) - - resumo_agent = codewise_instance.summary_specialist() - resumo_task = Task( - description="Com base no contexto da análise completa fornecida, crie um 'Resumo Executivo do Pull Request' **obrigatoriamente em Português do Brasil**, bem formatado em markdown, com 3-4 bullet points detalhados.", - expected_output="Um resumo executivo em markdown.", - agent=resumo_agent, - context=analysis_crew.tasks - ) - resultado_final = Crew(agents=[resumo_agent], tasks=[resumo_task]).kickoff() - - mentor_agent = codewise_instance.code_mentor() - mentor_task = Task( - description="Com base nas análises técnicas realizadas, comente e sugira recursos educacionais personalizados com base nas mudanças **obrigatoriamente em Português do Brasil**, bem formatado em markdown, com links que possuam conteúdo para melhorar o código.", - expected_output="Sugestões de melhoria.", - agent=mentor_agent, - context=analysis_crew.tasks - ) - resultado_mentor = Crew(agents=[mentor_agent],tasks=[mentor_task]).kickoff() - mentor_file_path = os.path.join(output_dir_path, "sugestoes_aprendizado.md") - try: - with open(mentor_file_path, "w", encoding="utf-8") as f: - f.write(str(resultado_mentor)) - print(f" - Arquivo 'sugestoes_aprendizado.md' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) - except Exception as e: - print(f" - ERRO ao salvar o arquivo 'sugestoes_aprendizado.md': {e}", file=sys.stderr) + + #salvando o julgamento lgpd + resultado_lgpd = lgpd_check.tasks[1].output + + # definindo caminho e nome do arquivo final. + lgpd_judge_file_path = os.path.join(output_dit_path, "julgamento_lgpd.md") + + try: + with open(lgpd_judge_file_path, "w", encoding="utf-8" as f): + f.write(str(resultado_lgpd)) + print(f" - Arquivo 'julgamento_lgpd.md' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) + except Exception as e: + print(f" - ERRO ao salvar o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) + + # fim + + # Verificação dos resultados da política de coleta de dados do provedor e model utilizados + status = False + try: + with open("julgamento_lgpd.md", "r") as julgamento: + if(julgamento.readline() == "sim"): + status = True + else: + status = False + + + if(status = True): + + print("Provedor requisitado está respeitando as normas LGPD, podemos proseguir com a análise.") + print(f"Acesse os arquivos 'analise_politica_coleta_de_dados.md.' e 'julgamento_lgpd.md' para comprovações do julgamento.") + + contexto_para_ia = "" + if modo == 'lint': + resultado_git = obter_mudancas_staged(caminho_repo) + + if resultado_git is None: + print("Nenhum problema aparente detectado.") + sys.exit(0) + + if resultado_git.startswith("AVISO:") or resultado_git.startswith("FALHA:"): + print(resultado_git) + sys.exit(0) + + contexto_para_ia = resultado_git + else: + if not gerar_entrada_automatica(caminho_repo, self.caminho_entrada, nome_branch): + sys.exit(0) + contexto_para_ia = self._ler_arquivo(self.caminho_entrada) - # Tentativa de colocar a analise lgpd para rodar antes do envio dos dados sensiveis - policy_agent = codewise_instance.dataCollect_policy_analytics() - provedor_usado = codewise_instance.provider - modelo_api_key = codewise_instance.model - - policy_analytics_task = Task( - description=f"Analise a documentação oficial da política de coleta de dados do provedor '{provedor_usado}' para o modelo '{modelo_api_key}'. Crie um relatório, **obrigatoriamente em Português do Brasil**, focado nos pontos-chave de como os dados de entrada do usuário (inputs de API) são tratados, incluindo coleta, uso e retenção.", - expected_output="Um relatório sobre a política de coleta de dados do modelo de api utilizado", - agent=policy_analytics_agent - ) - resultado_policy = Crew(agents=[policy_analytics_agent], tasks=[policy_analytics_task]).kickoff() - policy_file_path = os.path.join(output_dir_path, "analise_politica_coleta_de_dados.md") - try: - with open(policy_file_path, "w", encoding="utf-8") as f: - f.write(str(resultado_policy)) - print(f" - Arquivo 'analise_politica_coleta_de_dados.md' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) - except Exception as e: - print(f" - ERRO ao salvar o arquivo 'analise_politica_coleta_de_dados.md': {e}", file=sys.stderr) - # fim + codewise_instance = Codewise(commit_message=contexto_para_ia) + resultado_final = "" + + if modo == 'titulo': + agent = codewise_instance.summary_specialist() + task = Task(description=f"Crie um título de PR conciso no padrão Conventional Commits para as seguintes mudanças. A resposta deve ser APENAS o título, **obrigatoriamente em Português do Brasil**, sem aspas, acentos graves ou qualquer outro texto:\n{contexto_para_ia}", expected_output="Um único título de PR.", agent=agent) + resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() + + elif modo == 'descricao': + agent = codewise_instance.summary_specialist() + task = Task(description=f"Crie uma descrição de um parágrafo **obrigatoriamente em Português do Brasil** para um Pull Request para as seguintes mudanças:\n{contexto_para_ia}", expected_output="Um único parágrafo de texto.", agent=agent) + resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() + + elif modo == 'analise': + analysis_crew = codewise_instance.crew() + analysis_crew.kickoff(inputs={'input': contexto_para_ia}) + + print("Salvando relatórios de análise individuais...", file=sys.stderr) + + output_dir_name = "analises-concluidas" + output_dir_path = os.path.join(caminho_repo, output_dir_name) + os.makedirs(output_dir_path, exist_ok=True) + + keyword_map = { + "inspeção na estrutura do projeto": "arquitetura_atual.md", + "analise as integrações, bibliotecas externas e APIs": "analise_heuristicas_integracoes.md", + "aderência da mudança aos princípios S.O.L.I.D.": "analise_solid.md", + "aplicação correta ou ausência de padrões de projeto": "padroes_de_projeto.md" + } + + tasks_processed = {key: False for key in keyword_map} + + for task in analysis_crew.tasks: + for keyword, filename in keyword_map.items(): + if keyword in task.description and not tasks_processed[keyword]: + file_path = os.path.join(output_dir_path, filename) + try: + with open(file_path, "w", encoding="utf-8") as f: + f.write(str(task.output)) + print(f" - Arquivo '{filename}' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) + tasks_processed[keyword] = True + break + except Exception as e: + print(f" - ERRO ao salvar o arquivo '{filename}': {e}", file=sys.stderr) - elif modo == 'lint': - agent = codewise_instance.quality_consultant() - task = Task(description=f"Analise rapidamente as seguintes mudanças de código ('git diff') e aponte APENAS problemas óbvios ou code smells. A resposta deve ser **obrigatoriamente em Português do Brasil**. Seja conciso. Se não houver problemas, retorne 'Nenhum problema aparente detectado.'.\n\nCódigo a ser analisado:\n{contexto_para_ia}", expected_output="Uma lista curta em bullet points com sugestões, ou uma mensagem de que está tudo ok.", agent=agent) - resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() - - if os.path.exists(self.caminho_entrada): - os.remove(self.caminho_entrada) + resumo_agent = codewise_instance.summary_specialist() + resumo_task = Task( + description="Com base no contexto da análise completa fornecida, crie um 'Resumo Executivo do Pull Request' **obrigatoriamente em Português do Brasil**, bem formatado em markdown, com 3-4 bullet points detalhados.", + expected_output="Um resumo executivo em markdown.", + agent=resumo_agent, + context=analysis_crew.tasks + ) + resultado_final = Crew(agents=[resumo_agent], tasks=[resumo_task]).kickoff() + + mentor_agent = codewise_instance.code_mentor() + mentor_task = Task( + description="Com base nas análises técnicas realizadas, comente e sugira recursos educacionais personalizados com base nas mudanças **obrigatoriamente em Português do Brasil**, bem formatado em markdown, com links que possuam conteúdo para melhorar o código.", + expected_output="Sugestões de melhoria.", + agent=mentor_agent, + context=analysis_crew.tasks + ) + resultado_mentor = Crew(agents=[mentor_agent],tasks=[mentor_task]).kickoff() + mentor_file_path = os.path.join(output_dir_path, "sugestoes_aprendizado.md") + try: + with open(mentor_file_path, "w", encoding="utf-8") as f: + f.write(str(resultado_mentor)) + print(f" - Arquivo 'sugestoes_aprendizado.md' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) + except Exception as e: + print(f" - ERRO ao salvar o arquivo 'sugestoes_aprendizado.md': {e}", file=sys.stderr) + + elif modo == 'lint': + agent = codewise_instance.quality_consultant() + task = Task(description=f"Analise rapidamente as seguintes mudanças de código ('git diff') e aponte APENAS problemas óbvios ou code smells. A resposta deve ser **obrigatoriamente em Português do Brasil**. Seja conciso. Se não houver problemas, retorne 'Nenhum problema aparente detectado.'.\n\nCódigo a ser analisado:\n{contexto_para_ia}", expected_output="Uma lista curta em bullet points com sugestões, ou uma mensagem de que está tudo ok.", agent=agent) + resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() + + if os.path.exists(self.caminho_entrada): + os.remove(self.caminho_entrada) + + print(str(resultado_final).strip().replace('`', '')) - print(str(resultado_final).strip().replace('`', '')) + else: + print(f"Provedor requisitado não está dentro das normas LGPD! Por conta disso, as análises foram interrompidas antes que seus dados fossem enviados.") + print(f"Tente novamente escolhendo outro provedor ou modelo de api key.") + print(f"Para mais informações, acesse os arquivos 'analise_politica_coleta_de_dados.md.' e 'julgamento_lgpd.md'.") def _ler_arquivo(self, file_path: str) -> str: try: From 1520d1bd9b282102a760d9f72c735b8f784770a4 Mon Sep 17 00:00:00 2001 From: Matheus Magnago Date: Sun, 2 Nov 2025 11:33:47 -0300 Subject: [PATCH 08/36] Adding crewai tools (search) for the policy documentation of the provider's api key model and change cw_runner to be able to test the lgpd team (crew) --- codewise_lib/config/tasks.yaml | 2 +- codewise_lib/crew.py | 19 ++++++++++++------- codewise_lib/cw_runner.py | 29 +++++++++++++++++++---------- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/codewise_lib/config/tasks.yaml b/codewise_lib/config/tasks.yaml index 7b98682..5de3360 100644 --- a/codewise_lib/config/tasks.yaml +++ b/codewise_lib/config/tasks.yaml @@ -57,7 +57,7 @@ policy_analytics: lgpd_judging: description: > Com base nas leis LGPD e no relatório da analise da documentação, - determine se o provedor da api key está respeitando ou não as leis LGPD e justifique o julgamento apontando as provas, por meio de um relatório, onde a primeira linha do arquivo é a resposta antecipada (sim ou não). + determine se o provedor da api key está respeitando ou não as leis LGPD e justifique o julgamento apontando as provas, por meio de um relatório, onde a primeira linha **exatamente na linha 1** do arquivo é a resposta antecipada (sim ou não). expected_output: > Documento status_lgpd.md com as provas ou artigos utilizados para determinar a decisão tomada. agent: lgpd_judge \ No newline at end of file diff --git a/codewise_lib/crew.py b/codewise_lib/crew.py index b9db30b..1a35dd8 100644 --- a/codewise_lib/crew.py +++ b/codewise_lib/crew.py @@ -7,6 +7,10 @@ from crewai.project import CrewBase, agent, crew, task from .select_llm import create_llm +from crewai_tools import ( + WebsiteSearchTool +) + @CrewBase class Codewise: """Classe principal da crew Codewise""" @@ -17,7 +21,10 @@ def __init__(self, commit_message: str = ""): self.provider = os.getenv("AI_PROVIDER").upper() self.model = os.getenv("AI_MODEL") ## - self.llm = create_llm(provider,model) + self.llm = create_llm(self.provider,self.model) + + #tools + self.web_search_tool = WebsiteSearchTool() base_dir = os.path.dirname(os.path.abspath(__file__)) config_path = os.path.join(base_dir, "config") @@ -47,10 +54,10 @@ def summary_specialist(self) -> Agent: return Agent(config=self.agents_config['s def code_mentor(self) -> Agent: return Agent(config=self.agents_config['code_mentor'], llm=self.llm, verbose=False) @agent - def dataCollect_policy_analytics(self) -> Agent: return Agent(config=self.agents_config['dataCollect_policy_analytics'], llm=self.llm, verbose=False) + def dataCollect_policy_analytics(self) -> Agent: return Agent(config=self.agents_config['dataCollect_policy_analytics'], llm=self.llm, tools=[self.web_search_tool], verbose=False) @agent - def lgpd_judge(self) -> Agent: return Agent(config=self.agents_config['lgpd_judge'], llm=self.llm, verbose = False) + def lgpd_judge(self) -> Agent: return Agent(config=self.agents_config['lgpd_judge'], llm=self.llm, tools=[self.web_search_tool], verbose = False) @task def task_estrutura(self) -> Task: @@ -92,7 +99,7 @@ def task_policy(self) -> Task: @task def task_judging(self) -> Task: cfg = self.tasks_config['lgpd_judging'] - return Task(description=cfg['description'], expected_output=cfg['expected_output'], agent=self.task_policy_analytics()) + return Task(description=cfg['description'], expected_output=cfg['expected_output'], agent=self.lgpd_judge()) @crew @@ -103,15 +110,13 @@ def crew(self) -> Crew: process=Process.sequential ) - @crew def summary_crew(self) -> Crew: return Crew( agents=[self.summary_specialist()], tasks=[self.task_summarize()], process=Process.sequential ) - - @crew + def lgpd_crew(self) -> Crew: return Crew( agents=[self.dataCollect_policy_analytics(), self.lgpd_judge()], diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index dfffa81..3d6a9d4 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -18,10 +18,10 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): print(f"Verificando a política de coleta de dados do provedor com base neste modelo de api key...") #lgpd crew - lgpd_check = codewise_instance.lgpd_crew() + lgpd_check_crew = codewise_instance.lgpd_crew() # roda a analise e o julgamento lgpd (todo o time) - lgpd_check.kickoff() + lgpd_check_crew.kickoff() #definicao do caminho output_dir_name = "analises-julgamento-lgpd" @@ -29,7 +29,7 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): os.makedirs(output_dir_path, exist_ok=True) # salvando o resultado da analise da politica de coleta de dados - resultado_policy = lgpd_check.tasks[0].output + resultado_policy = lgpd_check_crew.tasks[0].output # definindo caminho e nome do arquivo final. policy_file_path = os.path.join(output_dir_path, "analise_politica_coleta_de_dados.md") @@ -43,14 +43,14 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): #salvando o julgamento lgpd - resultado_lgpd = lgpd_check.tasks[1].output + resultado_lgpd = lgpd_check_crew.tasks[1].output # definindo caminho e nome do arquivo final. - lgpd_judge_file_path = os.path.join(output_dit_path, "julgamento_lgpd.md") + lgpd_judge_file_path = os.path.join(output_dir_path, "julgamento_lgpd.md") try: - with open(lgpd_judge_file_path, "w", encoding="utf-8" as f): - f.write(str(resultado_lgpd)) + with open(lgpd_judge_file_path, "w", encoding="utf-8") as fj: + fj.write(str(resultado_lgpd)) print(f" - Arquivo 'julgamento_lgpd.md' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) except Exception as e: print(f" - ERRO ao salvar o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) @@ -61,12 +61,21 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): status = False try: with open("julgamento_lgpd.md", "r") as julgamento: + #print(julgamento.readline()) if(julgamento.readline() == "sim"): status = True + print("Provedor requisitado está respeitando as normas LGPD, podemos proseguir com a análise.") + print(f"Acesse os arquivos 'analise_politica_coleta_de_dados.md.' e 'julgamento_lgpd.md' para comprovações do julgamento.") else: status = False - - + print(f"Provedor requisitado não está dentro das normas LGPD! Por conta disso, as análises foram interrompidas antes que seus dados fossem enviados.") + print(f"Tente novamente escolhendo outro provedor ou modelo de api key.") + print(f"Para mais informações, acesse os arquivos 'analise_politica_coleta_de_dados.md.' e 'julgamento_lgpd.md'.") + except Exception as e: + print(f" - ERRO ao ler o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) + + print(status) + """ if(status = True): print("Provedor requisitado está respeitando as normas LGPD, podemos proseguir com a análise.") @@ -175,7 +184,7 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): print(f"Provedor requisitado não está dentro das normas LGPD! Por conta disso, as análises foram interrompidas antes que seus dados fossem enviados.") print(f"Tente novamente escolhendo outro provedor ou modelo de api key.") print(f"Para mais informações, acesse os arquivos 'analise_politica_coleta_de_dados.md.' e 'julgamento_lgpd.md'.") - + """ def _ler_arquivo(self, file_path: str) -> str: try: with open(file_path, "r", encoding="utf-8") as f: return f.read() From 1454e171b26a4cc84b01c4dd3c9023476c9dc10a Mon Sep 17 00:00:00 2001 From: Gustavo Saraiva Date: Sun, 2 Nov 2025 18:08:21 -0300 Subject: [PATCH 09/36] =?UTF-8?q?atualiza=C3=A7=C3=A3o=20do=20readme=20com?= =?UTF-8?q?=20as=20novas=20mudan=C3=A7as,=20corre=C3=A7=C3=A3o=20mensagem?= =?UTF-8?q?=20de=20erro=20de=20limite=20da=20api,=20adi=C3=A7=C3=A3o=20de?= =?UTF-8?q?=20depend=C3=AAncias=20ao=20requirements.txt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 253 ++++++++++++++++++++------------- codewise_lib/select_llm.py | 12 ++ requirements.txt | 11 +- scripts/codewise_review_win.py | 6 +- 4 files changed, 178 insertions(+), 104 deletions(-) diff --git a/README.md b/README.md index f62776c..19f9063 100644 --- a/README.md +++ b/README.md @@ -1,164 +1,217 @@ -# CodeWise +# 🧠 **CodeWise** -* Ferramenta instalável via pip que usa IA para analisar o código e automatizar a documentação de Pull Requests através de hooks do Git. +> **Ferramenta instalável via `pip` que usa IA para analisar o código e automatizar a documentação de Pull Requests através de hooks do Git.** -## Funcionalidades Principais -- **Geração de Título:** Cria títulos de PR claros e concisos seguindo o padrão *Conventional Commits*. -- **Geração de Descrição:** Escreve descrições detalhadas baseadas nas alterações do código. -- **Análise Técnica:** Posta um comentário no PR com um resumo executivo de melhorias de arquitetura, aderência a princípios S.O.L.I.D. e outros pontos de qualidade. -- **Automação com hooks:** Integra-se ao seu fluxo de trabalho Git para rodar automaticamente a cada `git commit` e `git push`. +--- + +## 🚀 **Funcionalidades Principais** + +- 🏷️ **Geração de Título:** Cria títulos de PR claros e concisos seguindo o padrão *Conventional Commits*. +- 📝 **Geração de Descrição:** Escreve descrições detalhadas baseadas nas alterações do código. +- 🧩 **Análise Técnica:** Posta um comentário no PR com um resumo executivo de melhorias de arquitetura, aderência a princípios S.O.L.I.D. e outros pontos de qualidade. +- 🔁 **Automação com Hooks:** Integra-se ao seu fluxo de trabalho Git para rodar automaticamente a cada `git commit` e `git push`. +- 🤖 **Flexibilidade de IA:** Escolha qual provedor de IA usar (`Cohere`, `Google Gemini`, `Groq`, `OpenAI`) através de uma simples configuração. +- 🔒 **Verificação de Privacidade (LGPD):** Analisa automaticamente a política de coleta de dados do provedor de IA antes de enviar o seu código. --- -## Guia de Instalação -Siga estes passos para instalar e configurar o CodeWise em qualquer um dos seus repositórios. +## ⚙️ **Guia de Instalação** + +Siga os passos abaixo para instalar e configurar o **CodeWise** em qualquer repositório. --- -### Passo 1: Pré-requisitos (ter no PC antes de tudo) +### 🧩 **Passo 1 — Pré-requisitos** + +Antes de começar, garanta que você tenha instaladas as seguintes ferramentas: + +1. **Python** (versão 3.11 ou superior) +2. **Git** +3. **GitHub CLI (`gh`)** -Antes de começar, garanta que você tenha as seguintes ferramentas instaladas em seu sistema: +> Após instalar a CLI do GitHub ([https://cli.github.com](https://cli.github.com)), execute: +> ```bash +> gh auth login +> ``` +> Faça login na sua conta — este passo é necessário apenas uma vez por computador. -1. **Python** (versão 3.11 ou superior). -2. **Git**. -3. **GitHub CLI (`gh`)**: Após instalar em (https://cli.github.com), logue com sua conta do GitHub executando `gh auth login` no seu terminal (só precisa fazer isso uma vez por PC). --- -### Passo 2: Configurando Seu Repositório +### 🧱 **Passo 2 — Configurando Seu Repositório** -**Para cada novo Repositório em que você desejar usar o CodeWise, siga os passos abaixo.** - -"*O ideal é sempre criar um ambiente virtual na pasta raiz do novo repositório para evitar conflitos das dependências.*" +> O ideal é sempre criar um **ambiente virtual na pasta raiz** do novo repositório para evitar conflitos de dependências. --- -#### 2.1 Crie e Utilize um Ambiente Virtual -Para evitar conflitos com outros projetos Python, use um ambiente virtual (`venv`). +#### 🔹 2.1 Criar e Ativar o Ambiente Virtual -* **Para Criar o Ambiente:** +**Crie o ambiente virtual** (dentro da raiz do repositório onde está a pasta `.git`): - * Este comando cria uma pasta `.venv` com uma instalação limpa do Python. Faça isso uma única vez por repositório, - *Lembrando que o ".venv" é o nome da pasta que foi criada, voce pode escolher qualquer outro nome pra ela.* - +```bash +# Windows +py -m venv .venv + +# Linux/WSL +python3 -m venv .venv +``` -(**dentro da raíz do repositório onde está a pasta .git**) +> 💡 O nome `.venv` é apenas uma convenção — você pode usar outro nome se quiser. - ```bash - # No Windows - py -m venv .venv - - # No Linux/WSL - python3 -m venv .venv - ``` +**Ative o ambiente:** -* **Para Ativar o Ambiente:** +```bash +# Windows (PowerShell) +.\.venv\Scripts\activate + +# Linux/WSL +source .venv/bin/activate +``` - * Sempre que for trabalhar no projeto, você precisa ativar o ambiente. +> ⚠️ Se ocorrer erro de política de execução no PowerShell, rode: +> ```bash +> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser +> ``` - * **Dica para Windows/PowerShell:** Se o comando de ativação der um erro de política de execução, rode este comando primeiro: `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser` +Você saberá que funcionou quando o nome `(.venv)` aparecer no início da linha do terminal. - ```bash - # No Windows (PowerShell) - .\.venv\Scripts\activate - - # No Linux/WSL - source .venv/bin/activate - ``` - *Você saberá que funcionou porque o nome `(.venv)` aparecerá no início da linha do seu terminal.* --- -#### 2.2 Instale a Ferramenta CodeWise -Com o ambiente virtual ativo, instale a biblioteca com o `pip`. + +#### 🔹 2.2 Instalar a Ferramenta CodeWise + +Com o ambiente virtual ativo, instale o pacote: ```bash pip install codewise-lib ``` - **Pode demorar um pouco pra instalar todas as dependências na primeira vez.** - -*Após instalar a lib, você pode confirmar se está tudo certo com o comando `codewise-help`* +> ⏳ A primeira instalação pode demorar um pouco. +> Após concluir, confirme se está tudo certo com: +> ```bash +> codewise-help +> ``` --- -#### 2.3 Configure a Chave da API (.env) -Para que a IA funcione, você precisa configurar sua chave da API do Google Gemini. +#### 🔹 2.3 Configurar a Chave da API (`.env`) + +Para que a IA funcione, configure suas chaves de API e o provedor desejado. + +1. **Na raiz do projeto**, crie um arquivo `.env`: + +```bash +# Windows +notepad .env + +# Linux/WSL +touch .env && nano .env +``` + +2. **Adicione o conteúdo abaixo e insira suas chaves:** + +```ini +# 1. ESCOLHA O PROVEDOR DE IA +# Opções disponíveis: "COHERE", "GROQ", "GEMINI", "OPENAI" +AI_PROVIDER="GEMINI" # -> maiúsculo!!! -1. **Na raiz do seu projeto**, crie um arquivo chamado `.env`. Você pode usar os seguintes comandos no terminal: +# 2. ESCOLHA O MODELO ESPECÍFICO +# Ex.: "gemini-2.0-flash", "gpt-4o-mini" +AI_MODEL=gemini-2.0-flash # -> *sem* aspas - * **Windows** - ```bash - notepad .env - ``` - * **Linux/WSL:** - ```bash - touch .env && nano .env - ``` +# 3. COLOQUE SUA(S) CHAVE(S) DE API +# A ferramenta usará a chave correta com base no AI_PROVIDER +COHERE_API_KEY=sua_chave_cohere_api_aqui +GROQ_API_KEY=sua_chave_groq_api_aqui +GEMINI_API_KEY=sua_chave_gemini_api_aqui +OPENAI_API_KEY=sua_chave_openai_api_aqui +``` -2. Dentro do arquivo `.env`, cole o seguinte conteúdo, adicione sua chave e salve: - ``` - GEMINI_API_KEY=SUA_CHAVE_AQUI - MODEL_NAME=gemini-2.0-flash - ``` - ⚠️ **Importante:** Lembre-se de adicionar o arquivo `.env` ao seu `.gitignore` para não enviar sua chave secreta para o GitHub ao dar push e que ele deve ser do tipo "arquivo ENV" e não .txt ou coisa do tipo. +> ⚠️ **Importante:** +> Adicione o arquivo `.env` ao `.gitignore` para evitar expor suas chaves secretas no GitHub. --- -## Nota Importante: A ferramenta CodeWise espera que seus remotes sigam a convenção padrão do GitHub: +### 🔸 **Nota Importante sobre Remotes** + +A ferramenta CodeWise espera que seus remotes sigam a convenção padrão do GitHub: + +- **origin** → aponta para o **seu fork pessoal** do repositório +- **upstream** → (opcional) aponta para o **repositório principal** -origin: Deve apontar para o seu fork pessoal do repositório. +> 🧠 Dica: +> Se o repositório for novo, execute um push inicial com: +> ```bash +> git push --no-verify +> ``` +> Isso garante que o `gh` funcione corretamente na criação dos Pull Requests. -upstream: (caso você adicione ao repositório)Deve apontar para o repositório principal do qual você fez o fork. +--- -**Caso você comece um repositório novo totalmente zerado, você tem que dar um push inicial com "git push --no-verify" antes de usar a ferramenta para que o GH CLI funcione corretamente ao criar os Pull Requests** +#### 🔹 2.4 Ativar a Automação no Repositório -#### 2.4 Agora apenas uma vez > Ative a Automação no Repositório com um comando. -Na raiz do projeto onde também está a pasta .git use: +Na raiz do projeto (onde está a pasta `.git`), execute **uma única vez**: ```bash codewise-init --all ``` -**Use esse comando sempre que você quiser mudar para onde o PULL REQUEST SERÁ CRIADO nos hooks de pre push, pois se você adicionar um remoto upstream você tem que alternar entre qual o PR será gerado.** -Aqui está a configuração do Alvo do Pull Request: +Esse comando adicionará automaticamente os hooks `pre-commit` e `pre-push`. -Se o seu repositório tiver um remote upstream configurado, o instalador fará uma pergunta depois que você usou o comando "codewise-init --all" -para definir o comportamento padrão do hook pre-push: +Se o seu repositório tiver um `upstream`, o instalador perguntará: - Um remote 'upstream' foi detectado. -Qual deve ser o comportamento padrão do 'git push' para este repositório? +``` +Um remote 'upstream' foi detectado. +Qual deve ser o comportamento padrão do 'git push'? 1: Criar Pull Request no 'origin' (seu fork) 2: Criar Pull Request no 'upstream' (projeto principal) Escolha o padrão (1 ou 2): +``` -Sua escolha será salva no hook, e você não precisará mais se preocupar com isso. Se não houver upstream, ele será configurado para origin por padrão. +> Sua escolha será salva no hook — não será necessário configurá-la novamente. +> Se não houver `upstream`, o padrão será `origin`. -Você verá uma mensagem de sucesso confirmando que a automação está ativa. +--- + +## 🧰 **Usando o CodeWise** -Com esse comando os arquivos de pre-commit e pre-push já terão sido adicionados ao seu hooks do repositório. +Com tudo configurado, você pode usar os comandos **`codewise-lint`** e **`codewise-pr`** manualmente ou automaticamente pelos hooks. --- -Tudo está funcionando agora no repositório que você configurou. -Caso queira instalar em um novo repositório basta repetir os passos. +### 🔸 **Fluxo de Uso** -# Usando o CodeWise -Com a configuração concluída, você já tem acesso aos comandos **codewise-lint** e **codewise-pr** de forma manual e automatizada após instalar os hooks. +#### 1️⃣ Adicione suas alterações +```bash +git add . +``` +> 💡 Use `codewise-lint` antes do commit para revisar seu código. -1. **Adicione suas alterações** +--- - * Após modificar seus arquivos, adicione-os à "staging area": - ```bash - git add . - ``` - * Aqui você já pode usar o comando `codewise-lint` para analisar os arquivos e você poder fazer ajustes antes de commitar. +#### 2️⃣ Faça o commit +```bash +git commit -m "implementa novo recurso" +``` +> O **hook `pre-commit`** será ativado e executará o `codewise-lint` automaticamente. + +--- + +#### 3️⃣ Envie para o GitHub +```bash +git push +``` +> O **hook `pre-push`** ativará o `codewise-pr`, que: +> - Perguntará para qual remote enviar (caso exista um `upstream`); +> - Criará ou atualizará o Pull Request com **título, descrição e análise técnica** gerados pela IA. -2. **Faça o commit** - ```bash - git commit -m "implementa novo recurso " - ``` - * Neste momento, o **hook `pre-commit` será ativado**, e o `codewise-lint` fará a análise rápida no seu terminal. +--- + +## 🛡️ **Verificação de Privacidade e LGPD** + +Antes de qualquer envio de código, o `codewise-lib` realiza uma **verificação de privacidade automática**. +O objetivo é garantir que o provedor de IA configurado no `.env` possua políticas compatíveis com a **LGPD**, assegurando a proteção dos seus dados e da sua base de código. + +--- -3. **Envie para o GitHub** - ```bash - git push - ``` - * Agora, o **hook `pre-push` será ativado**. O `codewise-pr` vai perguntar para qual remote você quer enviar caso haja um upstream além do seu origin em seguida irá criar um novo/atualizar seu Pull Request com título, descrição e análise técnica gerados pela IA. \ No newline at end of file +### ✅ **Tudo pronto!** +Seu repositório já está com o CodeWise ativo. +Para usar em outro repositório, basta repetir os passos acima. diff --git a/codewise_lib/select_llm.py b/codewise_lib/select_llm.py index 8c9921b..f09bce1 100644 --- a/codewise_lib/select_llm.py +++ b/codewise_lib/select_llm.py @@ -36,6 +36,18 @@ def create_llm(provider:str, model:str)-> LLM: model= "groq/" + model, temperature=0.7, ) + except Exception as e: + print(f"Erro ao inicializar o LLM. Verifique sua chave de API e dependências. Erro: {e}") + sys.exit(1) + elif provider == "COHERE": + if not os.getenv("COHERE_API_KEY"): + print("Erro: A variável de ambiente COHERE_API_KEY não foi definida.") + sys.exit(1) + try: + return LLM( + model= "cohere/" + model, + temperature=0.7, + ) except Exception as e: print(f"Erro ao inicializar o LLM. Verifique sua chave de API e dependências. Erro: {e}") sys.exit(1) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 23565c2..b653daf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,14 @@ crewai +crewai[tools] +qdrant-client python-dotenv pyyaml google-generativeai -langchain-google-genai \ No newline at end of file +langchain-google-genai +langchain-groq +groq +langchain-openai +openai +langchain-cohere +cohere +litellm \ No newline at end of file diff --git a/scripts/codewise_review_win.py b/scripts/codewise_review_win.py index 85f4096..1506d18 100644 --- a/scripts/codewise_review_win.py +++ b/scripts/codewise_review_win.py @@ -43,14 +43,14 @@ def run_codewise_mode(mode, repo_path, branch_name): # Imprime a mensagem amigável e formatada print(""" ================================================================ - ❌ ERRO: Limite de Uso da API do Google Atingido (Erro 429) + ❌ ERRO: Limite de Uso da API Atingido (Erro 429) ================================================================ A sua chave de API atingiu o limite máximo de requisições - permitido pelo plano gratuito do Google Gemini. + permitido pelo plano selecionado do modelo. Isso não é um bug no CodeWise, mas uma limitação da sua conta - na plataforma do Google. + na plataforma do provedor. . Aguarde: A cota do plano gratuito geralmente é renovada a cada 24 horas. Você pode tentar novamente amanhã. From 5480cb215ea67b127d7a582eb28c7bf22ba9e358 Mon Sep 17 00:00:00 2001 From: Matheus Magnago Date: Sun, 2 Nov 2025 18:48:45 -0300 Subject: [PATCH 10/36] testing with all tasks together --- codewise_lib/cw_runner.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index 3d6a9d4..c869703 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -16,7 +16,6 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): codewise_instance = Codewise() print(f"Verificando a política de coleta de dados do provedor com base neste modelo de api key...") - #lgpd crew lgpd_check_crew = codewise_instance.lgpd_crew() @@ -75,8 +74,8 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): print(f" - ERRO ao ler o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) print(status) - """ - if(status = True): + + if(status == True): print("Provedor requisitado está respeitando as normas LGPD, podemos proseguir com a análise.") print(f"Acesse os arquivos 'analise_politica_coleta_de_dados.md.' e 'julgamento_lgpd.md' para comprovações do julgamento.") @@ -184,7 +183,7 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): print(f"Provedor requisitado não está dentro das normas LGPD! Por conta disso, as análises foram interrompidas antes que seus dados fossem enviados.") print(f"Tente novamente escolhendo outro provedor ou modelo de api key.") print(f"Para mais informações, acesse os arquivos 'analise_politica_coleta_de_dados.md.' e 'julgamento_lgpd.md'.") - """ + def _ler_arquivo(self, file_path: str) -> str: try: with open(file_path, "r", encoding="utf-8") as f: return f.read() From 9de382d2845934db7c2be09c8593ca4b2ff30389 Mon Sep 17 00:00:00 2001 From: Matheus Magnago Date: Sun, 2 Nov 2025 22:19:33 -0300 Subject: [PATCH 11/36] lgpd crew working perfectly. Just running 2 times, on commit and then on push --- codewise_lib/config/tasks.yaml | 2 +- codewise_lib/cw_runner.py | 42 ++++++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/codewise_lib/config/tasks.yaml b/codewise_lib/config/tasks.yaml index 5de3360..f821351 100644 --- a/codewise_lib/config/tasks.yaml +++ b/codewise_lib/config/tasks.yaml @@ -59,5 +59,5 @@ lgpd_judging: Com base nas leis LGPD e no relatório da analise da documentação, determine se o provedor da api key está respeitando ou não as leis LGPD e justifique o julgamento apontando as provas, por meio de um relatório, onde a primeira linha **exatamente na linha 1** do arquivo é a resposta antecipada (sim ou não). expected_output: > - Documento status_lgpd.md com as provas ou artigos utilizados para determinar a decisão tomada. + Documento julgamento_lgpd.md com as provas ou artigos utilizados para determinar a decisão tomada. agent: lgpd_judge \ No newline at end of file diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index c869703..1a0274f 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -1,9 +1,11 @@ import os import sys +import re from .crew import Codewise from .entradagit import gerar_entrada_automatica, obter_mudancas_staged from crewai import Task, Crew + class CodewiseRunner: def __init__(self): self.BASE_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -16,6 +18,7 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): codewise_instance = Codewise() print(f"Verificando a política de coleta de dados do provedor com base neste modelo de api key...") + #lgpd crew lgpd_check_crew = codewise_instance.lgpd_crew() @@ -57,19 +60,38 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): # fim # Verificação dos resultados da política de coleta de dados do provedor e model utilizados + caminho = os.path.join("analises-julgamento-lgpd", "julgamento_lgpd.md") status = False + print("****************") + print("Arquivo existe?", os.path.exists(policy_file_path)) + print("Arquivo existe (status)?", os.path.exists(lgpd_judge_file_path)) + print("****************") try: - with open("julgamento_lgpd.md", "r") as julgamento: + with open(lgpd_judge_file_path, "r", encoding="utf-8") as julgamento: #print(julgamento.readline()) - if(julgamento.readline() == "sim"): - status = True - print("Provedor requisitado está respeitando as normas LGPD, podemos proseguir com a análise.") - print(f"Acesse os arquivos 'analise_politica_coleta_de_dados.md.' e 'julgamento_lgpd.md' para comprovações do julgamento.") - else: - status = False - print(f"Provedor requisitado não está dentro das normas LGPD! Por conta disso, as análises foram interrompidas antes que seus dados fossem enviados.") - print(f"Tente novamente escolhendo outro provedor ou modelo de api key.") - print(f"Para mais informações, acesse os arquivos 'analise_politica_coleta_de_dados.md.' e 'julgamento_lgpd.md'.") + + for linha in julgamento: + linha_clean = linha.strip().lower() + + linha_clean = re.sub(r'[*_#>`~]', '', linha_clean).strip() + + linha_clean = linha_clean.strip() + + print(f"{linha_clean}") + if(linha_clean == "sim"): + print(f"SSSSSSSIIIMMMMMMMMMFASOKFSAOFKAOFKAOS") + status = True + print("Provedor requisitado está respeitando as normas LGPD, podemos proseguir com a análise.") + print(f"Acesse os arquivos 'analise_politica_coleta_de_dados.md.' e 'julgamento_lgpd.md' para comprovações do julgamento.") + break + if (linha_clean == "não"): + status = False + print(f"NAAAAAAOFASOKFSAOFKAOFKAOS") + print("Provedor requisitado não está dentro das normas LGPD! Por conta disso, as análises foram interrompidas antes que seus dados fossem enviados.") + print(f"Tente novamente escolhendo outro provedor ou modelo de api key.") + print(f"Para mais informações, acesse os arquivos 'analise_politica_coleta_de_dados.md.' e 'julgamento_lgpd.md'.") + break + print(f"{linha}") except Exception as e: print(f" - ERRO ao ler o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) From f1d1bb9b7fddb608ed8fa7ad6d378d8736678ccd Mon Sep 17 00:00:00 2001 From: Matheus Magnago Date: Mon, 3 Nov 2025 21:46:57 -0300 Subject: [PATCH 12/36] Adding verification to see with already exists a LGPD analysics from the same provider and model --- codewise_lib/config/agents.yaml | 4 +- codewise_lib/config/tasks.yaml | 5 +- codewise_lib/cw_runner.py | 187 ++++++++++++++++++-------------- 3 files changed, 111 insertions(+), 85 deletions(-) diff --git a/codewise_lib/config/agents.yaml b/codewise_lib/config/agents.yaml index d9be0c7..3d08e02 100644 --- a/codewise_lib/config/agents.yaml +++ b/codewise_lib/config/agents.yaml @@ -32,8 +32,8 @@ code_mentor: dataCollect_policy_analytics: role: Especialista em análise de políticas de coletas de dados - goal: Analisar documentações sobre a política de coleta de dados de modelos de api keys fornecido por um provedor para uma futura comparação com as leis LGPD. - backstory: Analista de segurança de dados com foco na política de coleta de dados de um modelo de api key fornecido por um determinado provedor. + goal: Encontrar, a partir de buscas na web, e Analisar documentações sobre a política de coleta de dados de modelos de api keys fornecido por um provedor para uma futura comparação com as leis LGPD. + backstory: Analista de segurança de dados com foco na política de coleta de dados de um modelo de api key fornecido por um determinado provedor e que não desiste até efetuar a análise completa. lgpd_judge: role: Especialista em leis LGPD diff --git a/codewise_lib/config/tasks.yaml b/codewise_lib/config/tasks.yaml index f821351..d8c8204 100644 --- a/codewise_lib/config/tasks.yaml +++ b/codewise_lib/config/tasks.yaml @@ -49,15 +49,16 @@ mentoring_task: policy_analytics: description: > Crie um relatório sobre a política de coleta de dados do "{IA_PROVIDER}" para o modelo "{IA_MODEL}" - com base na documentação oficial. + com base, e apenas, na documentação oficial. Lembre-se de usar a ferramenta para buscar a documentação oficial. expected_output: > - Relatório data_collect_policy.md com um resumo dos principais pontos sobre a política de coleta de dados do provedor. + Relatório data_collect_policy.md com um resumo dos principais pontos REAIS sobre a política de coleta de dados do provedor a partir da documentação oficial, com as referências (links dos sites utilizados) no fim do arquivo e duas linhas logo ao inicio, reservadas informando APENAS E DIRETAMENTE o provedor e o modelo da api key, da seguinte forma: **providermodeloapikey Ex: coherecommand-a-03-2025**. agent: dataCollect_policy_analytics lgpd_judging: description: > Com base nas leis LGPD e no relatório da analise da documentação, determine se o provedor da api key está respeitando ou não as leis LGPD e justifique o julgamento apontando as provas, por meio de um relatório, onde a primeira linha **exatamente na linha 1** do arquivo é a resposta antecipada (sim ou não). + Lembre-se de usar a ferramenta para buscar as leis LGPD do site oficial (gov.br). expected_output: > Documento julgamento_lgpd.md com as provas ou artigos utilizados para determinar a decisão tomada. agent: lgpd_judge \ No newline at end of file diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index 1a0274f..f3b77f6 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -12,90 +12,19 @@ def __init__(self): self.caminho_entrada = os.path.join(self.BASE_DIR, ".entrada_temp.txt") def executar(self, caminho_repo: str, nome_branch: str, modo: str): - - # Tentativa de colocar a analise lgpd para rodar antes do envio dos dados sensiveis - # instancia sem passar o commit como contexto - codewise_instance = Codewise() - - print(f"Verificando a política de coleta de dados do provedor com base neste modelo de api key...") - - #lgpd crew - lgpd_check_crew = codewise_instance.lgpd_crew() - - # roda a analise e o julgamento lgpd (todo o time) - lgpd_check_crew.kickoff() - - #definicao do caminho - output_dir_name = "analises-julgamento-lgpd" - output_dir_path = os.path.join(caminho_repo, output_dir_name) - os.makedirs(output_dir_path, exist_ok=True) - - # salvando o resultado da analise da politica de coleta de dados - resultado_policy = lgpd_check_crew.tasks[0].output - - # definindo caminho e nome do arquivo final. - policy_file_path = os.path.join(output_dir_path, "analise_politica_coleta_de_dados.md") - - try: - with open(policy_file_path, "w", encoding="utf-8") as f: - f.write(str(resultado_policy)) - print(f" - Arquivo 'analise_politica_coleta_de_dados.md' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) - except Exception as e: - print(f" - ERRO ao salvar o arquivo 'analise_politica_coleta_de_dados.md': {e}", file=sys.stderr) - - - #salvando o julgamento lgpd - resultado_lgpd = lgpd_check_crew.tasks[1].output - - # definindo caminho e nome do arquivo final. - lgpd_judge_file_path = os.path.join(output_dir_path, "julgamento_lgpd.md") - try: - with open(lgpd_judge_file_path, "w", encoding="utf-8") as fj: - fj.write(str(resultado_lgpd)) - print(f" - Arquivo 'julgamento_lgpd.md' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) - except Exception as e: - print(f" - ERRO ao salvar o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) - - # fim + caminho_dir_lgpd = os.path.join(caminho_repo, "analises-julgamento-lgpd") + policy_file_path = os.path.join(caminho_dir_lgpd, "analise_politica_coleta_de_dados.md") + lgpd_judge_file_path = os.path.join(caminho_dir_lgpd, "julgamento_lgpd.md") - # Verificação dos resultados da política de coleta de dados do provedor e model utilizados - caminho = os.path.join("analises-julgamento-lgpd", "julgamento_lgpd.md") status = False - print("****************") - print("Arquivo existe?", os.path.exists(policy_file_path)) - print("Arquivo existe (status)?", os.path.exists(lgpd_judge_file_path)) - print("****************") - try: - with open(lgpd_judge_file_path, "r", encoding="utf-8") as julgamento: - #print(julgamento.readline()) - - for linha in julgamento: - linha_clean = linha.strip().lower() - - linha_clean = re.sub(r'[*_#>`~]', '', linha_clean).strip() - - linha_clean = linha_clean.strip() - - print(f"{linha_clean}") - if(linha_clean == "sim"): - print(f"SSSSSSSIIIMMMMMMMMMFASOKFSAOFKAOFKAOS") - status = True - print("Provedor requisitado está respeitando as normas LGPD, podemos proseguir com a análise.") - print(f"Acesse os arquivos 'analise_politica_coleta_de_dados.md.' e 'julgamento_lgpd.md' para comprovações do julgamento.") - break - if (linha_clean == "não"): - status = False - print(f"NAAAAAAOFASOKFSAOFKAOFKAOS") - print("Provedor requisitado não está dentro das normas LGPD! Por conta disso, as análises foram interrompidas antes que seus dados fossem enviados.") - print(f"Tente novamente escolhendo outro provedor ou modelo de api key.") - print(f"Para mais informações, acesse os arquivos 'analise_politica_coleta_de_dados.md.' e 'julgamento_lgpd.md'.") - break - print(f"{linha}") - except Exception as e: - print(f" - ERRO ao ler o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) - print(status) + if(self.verifica_se_existe_analise_lgpd(policy_file_path, lgpd_judge_file_path)): + print("Verificando o julgamento da análise existente...") + status = self.verify_result_judgement(lgpd_judge_file_path) + else: + print("Iniciando análise e julgamento LGPD...") + status = self.verify_lgpd(caminho_repo, caminho_dir_lgpd, policy_file_path, lgpd_judge_file_path) if(status == True): @@ -209,4 +138,100 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): def _ler_arquivo(self, file_path: str) -> str: try: with open(file_path, "r", encoding="utf-8") as f: return f.read() - except FileNotFoundError: return "" \ No newline at end of file + except FileNotFoundError: return "" + + def verify_lgpd(self, caminho_repo: str, caminho_dir_lgpd: str, policy_file_path: str, lgpd_judge_file_path: str) -> bool: + # Tentativa de colocar a analise lgpd para rodar antes do envio dos dados sensiveis + # instancia sem passar o commit como contexto + codewise_instance = Codewise() + + print(f"Verificando a política de coleta de dados do provedor com base neste modelo de api key...") + + #lgpd crew + lgpd_check_crew = codewise_instance.lgpd_crew() + + # roda a analise e o julgamento lgpd (todo o time) + lgpd_check_crew.kickoff() + + #definicao do caminho --> criacao directory + os.makedirs(caminho_dir_lgpd, exist_ok=True) + + # salvando o resultado da analise da politica de coleta de dados + resultado_policy = lgpd_check_crew.tasks[0].output + + # definindo caminho e nome do arquivo final. + #policy_file_path = os.path.join(output_dir_path, "analise_politica_coleta_de_dados.md") + + try: + with open(policy_file_path, "w", encoding="utf-8") as f: + f.write(str(resultado_policy)) + print(f" - Arquivo 'analise_politica_coleta_de_dados.md' salvo com sucesso em '{caminho_dir_lgpd}'.", file=sys.stderr) + except Exception as e: + print(f" - ERRO ao salvar o arquivo 'analise_politica_coleta_de_dados.md': {e}", file=sys.stderr) + + + #salvando o julgamento lgpd + resultado_lgpd = lgpd_check_crew.tasks[1].output + + # definindo caminho e nome do arquivo final. + #lgpd_judge_file_path = os.path.join(output_dir_path, "julgamento_lgpd.md") + + try: + with open(lgpd_judge_file_path, "w", encoding="utf-8") as fj: + fj.write(str(resultado_lgpd)) + print(f" - Arquivo 'julgamento_lgpd.md' salvo com sucesso em '{caminho_dir_lgpd}'.", file=sys.stderr) + except Exception as e: + print(f" - ERRO ao salvar o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) + + # fim + + # Verificação dos resultados da política de coleta de dados do provedor e model utilizados + return self.verify_result_judgement(lgpd_judge_file_path) + + def verify_result_judgement(self, lgpd_judge_file_path) -> bool: + try: + with open(lgpd_judge_file_path, "r", encoding="utf-8") as julgamento: + #print(julgamento.readline()) + + for linha in julgamento: + linha_clean = linha.strip().lower() + + linha_clean = re.sub(r'[*_#>`~]', '', linha_clean).strip() + + linha_clean = linha_clean.strip() + + if(linha_clean == "sim"): + return True + if (linha_clean == "não"): + return False + except Exception as e: + print(f" - ERRO ao ler o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) + + def verifica_se_existe_analise_lgpd(self, policy_file_path, lgpd_judge_file_path) -> bool: + + provider = os.getenv("AI_PROVIDER").lower() + model = os.getenv("AI_MODEL").lower() + model = re.sub(r'[*_#>`~]', '', model) + + provider_model = provider + model + + if(os.path.exists(policy_file_path) and os.path.exists(lgpd_judge_file_path)): + try: + with open(policy_file_path, "r", encoding="utf-8") as f: + #print(julgamento.readline()) + + for linha in f: + linha_clean = linha.strip().lower() + + linha_clean = re.sub(r'[*_#>`~]', '', linha_clean).strip() + + linha_clean = linha_clean.strip() + if(linha_clean == provider_model): + print("A análise e verificação LGPD já foi feita anteriormente para este mesmo provedor e modelo api key. Por conta disso, não será necessário efetuá-la novamente!") + return True + if(not linha): + return False + except Exception as e: + print(f" - ERRO ao ler o arquivo 'analise_politica_coleta_de_dados.md': {e}", file=sys.stderr) + else: + return False \ No newline at end of file From 98bb65f1d20d207e9f4ed9285cd3dd17cdc7a532 Mon Sep 17 00:00:00 2001 From: Matheus Magnago Date: Tue, 4 Nov 2025 20:37:00 -0300 Subject: [PATCH 13/36] fixing bugs --- codewise_lib/config/tasks.yaml | 1 + codewise_lib/cw_runner.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/codewise_lib/config/tasks.yaml b/codewise_lib/config/tasks.yaml index d8c8204..2b72608 100644 --- a/codewise_lib/config/tasks.yaml +++ b/codewise_lib/config/tasks.yaml @@ -37,6 +37,7 @@ summarize_analysis: escreva um resumo objetivo e claro descrevendo o que foi implementado no código analisado com base nas boas práticas de Pull Request. expected_output: > Um parágrafo bem estruturado com as alterações realizadas, pronto para ser usado como descrição de um Pull Request em (pt-br). + agent: summary_specialist mentoring_task: description: > diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index f3b77f6..c56b292 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -153,7 +153,7 @@ def verify_lgpd(self, caminho_repo: str, caminho_dir_lgpd: str, policy_file_path # roda a analise e o julgamento lgpd (todo o time) lgpd_check_crew.kickoff() - #definicao do caminho --> criacao directory + # cria directory os.makedirs(caminho_dir_lgpd, exist_ok=True) # salvando o resultado da analise da politica de coleta de dados From 9b1da718cc3491b20928791e2d65e8de7368e472 Mon Sep 17 00:00:00 2001 From: Matheus Magnago Date: Tue, 4 Nov 2025 20:57:24 -0300 Subject: [PATCH 14/36] Fixing the task output prompt --- codewise_lib/config/tasks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codewise_lib/config/tasks.yaml b/codewise_lib/config/tasks.yaml index 2b72608..a9e3e73 100644 --- a/codewise_lib/config/tasks.yaml +++ b/codewise_lib/config/tasks.yaml @@ -52,7 +52,7 @@ policy_analytics: Crie um relatório sobre a política de coleta de dados do "{IA_PROVIDER}" para o modelo "{IA_MODEL}" com base, e apenas, na documentação oficial. Lembre-se de usar a ferramenta para buscar a documentação oficial. expected_output: > - Relatório data_collect_policy.md com um resumo dos principais pontos REAIS sobre a política de coleta de dados do provedor a partir da documentação oficial, com as referências (links dos sites utilizados) no fim do arquivo e duas linhas logo ao inicio, reservadas informando APENAS E DIRETAMENTE o provedor e o modelo da api key, da seguinte forma: **providermodeloapikey Ex: coherecommand-a-03-2025**. + Relatório data_collect_policy.md com um resumo dos principais pontos REAIS sobre a política de coleta de dados do provedor a partir da documentação oficial, com as referências (links dos sites utilizados) no fim do arquivo e duas linhas logo ao inicio, reservadas informando APENAS E DIRETAMENTE o provedor e o modelo da api key, da seguinte forma: **providermodeloapikey**. agent: dataCollect_policy_analytics lgpd_judging: From c9b8dccaab2b3eb2e5ec017372954f729de09a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Ren=C3=A3?= Date: Tue, 4 Nov 2025 21:09:02 -0300 Subject: [PATCH 15/36] atualizando requirements e agente de lgpd --- codewise_lib/config/tasks.yaml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codewise_lib/config/tasks.yaml b/codewise_lib/config/tasks.yaml index 2b72608..5a41872 100644 --- a/codewise_lib/config/tasks.yaml +++ b/codewise_lib/config/tasks.yaml @@ -52,7 +52,7 @@ policy_analytics: Crie um relatório sobre a política de coleta de dados do "{IA_PROVIDER}" para o modelo "{IA_MODEL}" com base, e apenas, na documentação oficial. Lembre-se de usar a ferramenta para buscar a documentação oficial. expected_output: > - Relatório data_collect_policy.md com um resumo dos principais pontos REAIS sobre a política de coleta de dados do provedor a partir da documentação oficial, com as referências (links dos sites utilizados) no fim do arquivo e duas linhas logo ao inicio, reservadas informando APENAS E DIRETAMENTE o provedor e o modelo da api key, da seguinte forma: **providermodeloapikey Ex: coherecommand-a-03-2025**. + Relatório analise_politica_coleta_de_dados.md com um resumo dos principais pontos REAIS sobre a política de coleta de dados do provedor a partir da documentação oficial, com as referências (links dos sites utilizados) no fim do arquivo e duas linhas logo ao inicio, reservadas informando APENAS E DIRETAMENTE o provedor e o modelo da api key, da seguinte forma: **providermodeloapikey**. agent: dataCollect_policy_analytics lgpd_judging: diff --git a/requirements.txt b/requirements.txt index afaab27..f0d264e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ qdrant-client python-dotenv pyyaml google-generativeai -langchain-google-genai +google-genai langchain-groq groq langchain-openai From 36f6a9a839b799f2179230ed2c2340117ecc46a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Ren=C3=A3?= Date: Wed, 19 Nov 2025 14:43:42 -0300 Subject: [PATCH 16/36] fix commit --- codewise_lib/create_llm.py | 67 -------------------------------------- codewise_lib/select_llm.py | 2 +- requirements.txt | 6 ---- 3 files changed, 1 insertion(+), 74 deletions(-) delete mode 100644 codewise_lib/create_llm.py diff --git a/codewise_lib/create_llm.py b/codewise_lib/create_llm.py deleted file mode 100644 index 9807c7b..0000000 --- a/codewise_lib/create_llm.py +++ /dev/null @@ -1,67 +0,0 @@ -import os -import sys -from dotenv import load_dotenv - -#import dos modelos de api que o codewise vai suportar -try: - from langchain_google_genai import ChatGoogleGenerativeAI - from langchain_openai import ChatOpenAI - -# trata erros de importação caso as libs não estejam instaladas -# instalar: pip install -r requirements.txt (adicionar as outras llms no requirements.txt) -except ImportError: - print("Erro: Bibliotecas de LLM (ex: langchain-google-genai, langchain-openai) não encontradas.", file=sys.stderr) - print("Instale as dependências necessárias (verifique o requirements.txt).", file=sys.stderr) - sys.exit(1) - -def create_llm(): - """ - Lê as variáveis de ambiente e instancia o modelo de LLM selecionado. - """ - load_dotenv() - - provider = os.getenv("AI_PROVIDER", "google").lower() # só de garantia, se não for definido, setei a do goodle como padrão - # o modelo acho que é válido ser opcional, pro usuário escolher o modelo padrão do provider se quiser - # acho que resolve aquela questão de querer o gemini pro, fica a critério do usuário - model_name = os.getenv("AI_MODEL") - - print(f"--- 🤖 Inicializando IA com o provedor: {provider} ---", file=sys.stderr) - - try: - if provider == "google": - api_key = os.getenv("GEMINI_API_KEY") - if not api_key: - print("Erro: AI_PROVIDER='google', mas GEMINI_API_KEY não foi definida no .env", file=sys.stderr) - sys.exit(1) - - model = model_name or "gemini-2.0-flash" # se não for definido, usa a versão gratuita como padrão - print(f"Usando Google (Gemini) - Modelo: {model}", file=sys.stderr) - return ChatGoogleGenerativeAI( - model_name=model, - google_api_key=api_key - ) - - elif provider == "openai": - api_key = os.getenv("OPENAI_API_KEY") - if not api_key: - print("Erro: AI_PROVIDER='openai', mas OPENAI_API_KEY não foi definida no .env", file=sys.stderr) - sys.exit(1) - - model = model_name or "gpt-4o-mini" #mesma coisa, usa esse modelo como padrão se não for definido um específicosw - print(f"Usando OpenAI - Modelo: {model}", file=sys.stderr) - return ChatOpenAI( - model_name=model, - api_key=api_key - ) - - # se quisermos, da pra adicionar outras llms aqui - - else: - print(f"Erro: AI_PROVIDER '{provider}' não é suportado. (Use 'google' ou 'openai')", file=sys.stderr) - sys.exit(1) - - except Exception as e: - print(f"Erro ao inicializar o LLM para o provider '{provider}'.", file=sys.stderr) - print(f"Verifique suas chaves de API e se o modelo '{model_name}' é válido.", file=sys.stderr) - print(f"Erro original: {e}") - sys.exit(1) \ No newline at end of file diff --git a/codewise_lib/select_llm.py b/codewise_lib/select_llm.py index f09bce1..71fcd6e 100644 --- a/codewise_lib/select_llm.py +++ b/codewise_lib/select_llm.py @@ -45,7 +45,7 @@ def create_llm(provider:str, model:str)-> LLM: sys.exit(1) try: return LLM( - model= "cohere/" + model, + model= "cohere_chat/" + model, temperature=0.7, ) except Exception as e: diff --git a/requirements.txt b/requirements.txt index f0d264e..d21a10b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,10 +5,4 @@ python-dotenv pyyaml google-generativeai google-genai -langchain-groq -groq -langchain-openai -openai -langchain-cohere -cohere litellm From 85df4b4eaf0a74c4cc1ffe9ffaf4b870d9453fac Mon Sep 17 00:00:00 2001 From: Matheus Magnago Date: Wed, 19 Nov 2025 20:50:45 -0300 Subject: [PATCH 17/36] LGPD user input verify --- codewise_lib/cw_runner.py | 205 +++++++++++++++--------------- codewise_lib/main.py | 2 +- requirements.txt | 219 ++++++++++++++++++++++++++++++--- scripts/codewise_review_win.py | 61 +++++++++ 4 files changed, 367 insertions(+), 120 deletions(-) diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index c56b292..2168033 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -18,122 +18,116 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): lgpd_judge_file_path = os.path.join(caminho_dir_lgpd, "julgamento_lgpd.md") status = False - + if(self.verifica_se_existe_analise_lgpd(policy_file_path, lgpd_judge_file_path)): print("Verificando o julgamento da análise existente...") status = self.verify_result_judgement(lgpd_judge_file_path) else: print("Iniciando análise e julgamento LGPD...") status = self.verify_lgpd(caminho_repo, caminho_dir_lgpd, policy_file_path, lgpd_judge_file_path) - - if(status == True): - print("Provedor requisitado está respeitando as normas LGPD, podemos proseguir com a análise.") - print(f"Acesse os arquivos 'analise_politica_coleta_de_dados.md.' e 'julgamento_lgpd.md' para comprovações do julgamento.") - - contexto_para_ia = "" + if(modo == 'lgpd_verify'): + return 0 + + contexto_para_ia = "" - if modo == 'lint': - resultado_git = obter_mudancas_staged(caminho_repo) - - if resultado_git is None: - print("Nenhum problema aparente detectado.") - sys.exit(0) - - if resultado_git.startswith("AVISO:") or resultado_git.startswith("FALHA:"): - print(resultado_git) - sys.exit(0) - - contexto_para_ia = resultado_git - else: - if not gerar_entrada_automatica(caminho_repo, self.caminho_entrada, nome_branch): - sys.exit(0) - contexto_para_ia = self._ler_arquivo(self.caminho_entrada) + if modo == 'lint': + resultado_git = obter_mudancas_staged(caminho_repo) - codewise_instance = Codewise(commit_message=contexto_para_ia) - resultado_final = "" - - if modo == 'titulo': - agent = codewise_instance.summary_specialist() - task = Task(description=f"Crie um título de PR conciso no padrão Conventional Commits para as seguintes mudanças. A resposta deve ser APENAS o título, **obrigatoriamente em Português do Brasil**, sem aspas, acentos graves ou qualquer outro texto:\n{contexto_para_ia}", expected_output="Um único título de PR.", agent=agent) - resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() - - elif modo == 'descricao': - agent = codewise_instance.summary_specialist() - task = Task(description=f"Crie uma descrição de um parágrafo **obrigatoriamente em Português do Brasil** para um Pull Request para as seguintes mudanças:\n{contexto_para_ia}", expected_output="Um único parágrafo de texto.", agent=agent) - resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() - - elif modo == 'analise': - analysis_crew = codewise_instance.crew() - analysis_crew.kickoff(inputs={'input': contexto_para_ia}) - - print("Salvando relatórios de análise individuais...", file=sys.stderr) - - output_dir_name = "analises-concluidas" - output_dir_path = os.path.join(caminho_repo, output_dir_name) - os.makedirs(output_dir_path, exist_ok=True) - - keyword_map = { - "inspeção na estrutura do projeto": "arquitetura_atual.md", - "analise as integrações, bibliotecas externas e APIs": "analise_heuristicas_integracoes.md", - "aderência da mudança aos princípios S.O.L.I.D.": "analise_solid.md", - "aplicação correta ou ausência de padrões de projeto": "padroes_de_projeto.md" - } - - tasks_processed = {key: False for key in keyword_map} - - for task in analysis_crew.tasks: - for keyword, filename in keyword_map.items(): - if keyword in task.description and not tasks_processed[keyword]: - file_path = os.path.join(output_dir_path, filename) - try: - with open(file_path, "w", encoding="utf-8") as f: - f.write(str(task.output)) - print(f" - Arquivo '{filename}' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) - tasks_processed[keyword] = True - break - except Exception as e: - print(f" - ERRO ao salvar o arquivo '{filename}': {e}", file=sys.stderr) - - resumo_agent = codewise_instance.summary_specialist() - resumo_task = Task( - description="Com base no contexto da análise completa fornecida, crie um 'Resumo Executivo do Pull Request' **obrigatoriamente em Português do Brasil**, bem formatado em markdown, com 3-4 bullet points detalhados.", - expected_output="Um resumo executivo em markdown.", - agent=resumo_agent, - context=analysis_crew.tasks - ) - resultado_final = Crew(agents=[resumo_agent], tasks=[resumo_task]).kickoff() - - mentor_agent = codewise_instance.code_mentor() - mentor_task = Task( - description="Com base nas análises técnicas realizadas, comente e sugira recursos educacionais personalizados com base nas mudanças **obrigatoriamente em Português do Brasil**, bem formatado em markdown, com links que possuam conteúdo para melhorar o código.", - expected_output="Sugestões de melhoria.", - agent=mentor_agent, - context=analysis_crew.tasks - ) - resultado_mentor = Crew(agents=[mentor_agent],tasks=[mentor_task]).kickoff() - mentor_file_path = os.path.join(output_dir_path, "sugestoes_aprendizado.md") - try: - with open(mentor_file_path, "w", encoding="utf-8") as f: - f.write(str(resultado_mentor)) - print(f" - Arquivo 'sugestoes_aprendizado.md' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) - except Exception as e: - print(f" - ERRO ao salvar o arquivo 'sugestoes_aprendizado.md': {e}", file=sys.stderr) - - elif modo == 'lint': - agent = codewise_instance.quality_consultant() - task = Task(description=f"Analise rapidamente as seguintes mudanças de código ('git diff') e aponte APENAS problemas óbvios ou code smells. A resposta deve ser **obrigatoriamente em Português do Brasil**. Seja conciso. Se não houver problemas, retorne 'Nenhum problema aparente detectado.'.\n\nCódigo a ser analisado:\n{contexto_para_ia}", expected_output="Uma lista curta em bullet points com sugestões, ou uma mensagem de que está tudo ok.", agent=agent) - resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() + if resultado_git is None: + print("Nenhum problema aparente detectado.") + sys.exit(0) - if os.path.exists(self.caminho_entrada): - os.remove(self.caminho_entrada) + if resultado_git.startswith("AVISO:") or resultado_git.startswith("FALHA:"): + print(resultado_git) + sys.exit(0) + + contexto_para_ia = resultado_git + else: + if not gerar_entrada_automatica(caminho_repo, self.caminho_entrada, nome_branch): + sys.exit(0) + contexto_para_ia = self._ler_arquivo(self.caminho_entrada) + + codewise_instance = Codewise(commit_message=contexto_para_ia) + resultado_final = "" + + if modo == 'titulo': + agent = codewise_instance.summary_specialist() + task = Task(description=f"Crie um título de PR conciso no padrão Conventional Commits para as seguintes mudanças. A resposta deve ser APENAS o título, **obrigatoriamente em Português do Brasil**, sem aspas, acentos graves ou qualquer outro texto:\n{contexto_para_ia}", expected_output="Um único título de PR.", agent=agent) + resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() + + elif modo == 'descricao': + agent = codewise_instance.summary_specialist() + task = Task(description=f"Crie uma descrição de um parágrafo **obrigatoriamente em Português do Brasil** para um Pull Request para as seguintes mudanças:\n{contexto_para_ia}", expected_output="Um único parágrafo de texto.", agent=agent) + resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() + + elif modo == 'analise': + analysis_crew = codewise_instance.crew() + analysis_crew.kickoff(inputs={'input': contexto_para_ia}) + + print("Salvando relatórios de análise individuais...", file=sys.stderr) + + output_dir_name = "analises-concluidas" + output_dir_path = os.path.join(caminho_repo, output_dir_name) + os.makedirs(output_dir_path, exist_ok=True) + + keyword_map = { + "inspeção na estrutura do projeto": "arquitetura_atual.md", + "analise as integrações, bibliotecas externas e APIs": "analise_heuristicas_integracoes.md", + "aderência da mudança aos princípios S.O.L.I.D.": "analise_solid.md", + "aplicação correta ou ausência de padrões de projeto": "padroes_de_projeto.md" + } + + tasks_processed = {key: False for key in keyword_map} + + for task in analysis_crew.tasks: + for keyword, filename in keyword_map.items(): + if keyword in task.description and not tasks_processed[keyword]: + file_path = os.path.join(output_dir_path, filename) + try: + with open(file_path, "w", encoding="utf-8") as f: + f.write(str(task.output)) + print(f" - Arquivo '{filename}' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) + tasks_processed[keyword] = True + break + except Exception as e: + print(f" - ERRO ao salvar o arquivo '{filename}': {e}", file=sys.stderr) + + resumo_agent = codewise_instance.summary_specialist() + resumo_task = Task( + description="Com base no contexto da análise completa fornecida, crie um 'Resumo Executivo do Pull Request' **obrigatoriamente em Português do Brasil**, bem formatado em markdown, com 3-4 bullet points detalhados.", + expected_output="Um resumo executivo em markdown.", + agent=resumo_agent, + context=analysis_crew.tasks + ) + resultado_final = Crew(agents=[resumo_agent], tasks=[resumo_task]).kickoff() + + mentor_agent = codewise_instance.code_mentor() + mentor_task = Task( + description="Com base nas análises técnicas realizadas, comente e sugira recursos educacionais personalizados com base nas mudanças **obrigatoriamente em Português do Brasil**, bem formatado em markdown, com links que possuam conteúdo para melhorar o código.", + expected_output="Sugestões de melhoria.", + agent=mentor_agent, + context=analysis_crew.tasks + ) + resultado_mentor = Crew(agents=[mentor_agent],tasks=[mentor_task]).kickoff() + mentor_file_path = os.path.join(output_dir_path, "sugestoes_aprendizado.md") + try: + with open(mentor_file_path, "w", encoding="utf-8") as f: + f.write(str(resultado_mentor)) + print(f" - Arquivo 'sugestoes_aprendizado.md' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) + except Exception as e: + print(f" - ERRO ao salvar o arquivo 'sugestoes_aprendizado.md': {e}", file=sys.stderr) + + elif modo == 'lint': + agent = codewise_instance.quality_consultant() + task = Task(description=f"Analise rapidamente as seguintes mudanças de código ('git diff') e aponte APENAS problemas óbvios ou code smells. A resposta deve ser **obrigatoriamente em Português do Brasil**. Seja conciso. Se não houver problemas, retorne 'Nenhum problema aparente detectado.'.\n\nCódigo a ser analisado:\n{contexto_para_ia}", expected_output="Uma lista curta em bullet points com sugestões, ou uma mensagem de que está tudo ok.", agent=agent) + resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() + + if os.path.exists(self.caminho_entrada): + os.remove(self.caminho_entrada) - print(str(resultado_final).strip().replace('`', '')) + print(str(resultado_final).strip().replace('`', '')) - else: - print(f"Provedor requisitado não está dentro das normas LGPD! Por conta disso, as análises foram interrompidas antes que seus dados fossem enviados.") - print(f"Tente novamente escolhendo outro provedor ou modelo de api key.") - print(f"Para mais informações, acesse os arquivos 'analise_politica_coleta_de_dados.md.' e 'julgamento_lgpd.md'.") def _ler_arquivo(self, file_path: str) -> str: try: @@ -234,4 +228,5 @@ def verifica_se_existe_analise_lgpd(self, policy_file_path, lgpd_judge_file_path except Exception as e: print(f" - ERRO ao ler o arquivo 'analise_politica_coleta_de_dados.md': {e}", file=sys.stderr) else: - return False \ No newline at end of file + return False + \ No newline at end of file diff --git a/codewise_lib/main.py b/codewise_lib/main.py index 3df954b..0ba6da2 100644 --- a/codewise_lib/main.py +++ b/codewise_lib/main.py @@ -10,7 +10,7 @@ def main(): "--mode", type=str, required=True, - choices=['descricao', 'analise', 'titulo', 'lint'], + choices=['descricao', 'analise', 'titulo', 'lint', 'lgpd_verify'], help="Modo de operação." ) args = parser.parse_args() diff --git a/requirements.txt b/requirements.txt index b653daf..f64d25c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,205 @@ -crewai -crewai[tools] -qdrant-client -python-dotenv -pyyaml -google-generativeai -langchain-google-genai -langchain-groq -groq -langchain-openai -openai -langchain-cohere -cohere -litellm \ No newline at end of file +aiohappyeyeballs==2.6.1 +aiohttp==3.13.0 +aiosignal==1.4.0 +annotated-types==0.7.0 +anthropic==0.72.0 +anyio==4.11.0 +appdirs==1.4.4 +asttokens==3.0.0 +attrs==25.4.0 +backoff==2.2.1 +bcrypt==5.0.0 +beautifulsoup4==4.14.2 +blinker==1.9.0 +browserbase==1.4.0 +build==1.3.0 +cachetools==6.2.0 +certifi==2025.10.5 +cffi==2.0.0 +charset-normalizer==3.4.3 +chromadb==1.1.1 +click==8.3.0 +cohere==5.20.0 +colorama==0.4.6 +coloredlogs==15.0.1 +crewai==0.201.1 +crewai-tools==0.76.0 +cryptography==46.0.2 +dataclasses-json==0.6.7 +decorator==5.2.1 +defusedxml==0.7.1 +deprecation==2.1.0 +diskcache==5.6.3 +distro==1.9.0 +docker==7.1.0 +docstring_parser==0.17.0 +dotenv==0.9.9 +durationpy==0.10 +et_xmlfile==2.0.0 +executing==2.2.1 +fastavro==1.12.1 +filelock==3.19.1 +filetype==1.2.0 +flatbuffers==25.9.23 +frozenlist==1.8.0 +fsspec==2025.9.0 +google-ai-generativelanguage==0.6.15 +google-api-core==2.25.2 +google-api-python-client==2.184.0 +google-auth==2.41.1 +google-auth-httplib2==0.2.0 +google-genai==1.48.0 +google-generativeai==0.8.5 +googleapis-common-protos==1.70.0 +greenlet==3.2.4 +groq==0.33.0 +grpcio==1.75.1 +grpcio-status==1.71.2 +h11==0.16.0 +h2==4.3.0 +hpack==4.1.0 +httpcore==1.0.9 +httplib2==0.31.0 +httptools==0.6.4 +httpx==0.28.1 +httpx-sse==0.4.0 +huggingface-hub==0.35.3 +humanfriendly==10.0 +hyperframe==6.1.0 +idna==3.10 +importlib_metadata==8.7.0 +importlib_resources==6.5.2 +instructor==1.11.3 +ipython==9.6.0 +ipython_pygments_lexers==1.1.1 +jedi==0.19.2 +Jinja2==3.1.6 +jiter==0.10.0 +json5==0.12.1 +json_repair==0.25.2 +jsonpatch==1.33 +jsonpickle==4.1.1 +jsonpointer==3.0.0 +jsonref==1.1.0 +jsonschema==4.25.1 +jsonschema-specifications==2025.9.1 +kubernetes==34.1.0 +lance-namespace==0.0.20 +lance-namespace-urllib3-client==0.0.20 +lancedb==0.25.2 +langchain-classic==1.0.0 +langchain-cohere==0.5.0 +langchain-community==0.4.1 +langchain-core==1.0.3 +langchain-google-genai==2.0.10 +langchain-groq==1.0.0 +langchain-openai==1.0.2 +langchain-text-splitters==1.0.0 +langsmith==0.4.33 +litellm==1.74.9 +lxml==6.0.2 +markdown-it-py==4.0.0 +MarkupSafe==3.0.3 +marshmallow==3.26.1 +matplotlib-inline==0.1.7 +mdurl==0.1.2 +mmh3==5.2.0 +mpmath==1.3.0 +multidict==6.7.0 +mypy_extensions==1.1.0 +nest-asyncio==1.6.0 +networkx==3.5 +numpy==2.3.3 +oauthlib==3.3.1 +onnxruntime==1.23.0 +openai==1.109.1 +openpyxl==3.1.5 +opentelemetry-api==1.37.0 +opentelemetry-exporter-otlp-proto-common==1.37.0 +opentelemetry-exporter-otlp-proto-grpc==1.37.0 +opentelemetry-exporter-otlp-proto-http==1.37.0 +opentelemetry-proto==1.37.0 +opentelemetry-sdk==1.37.0 +opentelemetry-semantic-conventions==0.58b0 +orjson==3.11.3 +overrides==7.7.0 +packaging==25.0 +parso==0.8.5 +pdfminer.six==20250506 +pdfplumber==0.11.7 +pillow==11.3.0 +playwright==1.55.0 +portalocker==2.7.0 +posthog==5.4.0 +prompt_toolkit==3.0.52 +propcache==0.4.0 +proto-plus==1.26.1 +protobuf==5.29.5 +pure_eval==0.2.3 +pyarrow==22.0.0 +pyasn1==0.6.1 +pyasn1_modules==0.4.2 +pybase64==1.4.2 +pycparser==2.23 +pydantic==2.12.0 +pydantic-settings==2.11.0 +pydantic_core==2.41.1 +pyee==13.0.0 +Pygments==2.19.2 +PyJWT==2.10.1 +pylance==0.39.0 +pyparsing==3.2.5 +pypdf==6.1.3 +pypdfium2==4.30.0 +PyPika==0.48.9 +pyproject_hooks==1.2.0 +pyreadline3==3.5.4 +python-dateutil==2.9.0.post0 +python-docx==1.2.0 +python-dotenv==1.1.1 +pytube==15.0.0 +pyvis==0.3.2 +pywin32==311 +PyYAML==6.0.3 +qdrant-client==1.15.1 +referencing==0.36.2 +regex==2025.9.18 +requests==2.32.5 +requests-oauthlib==2.0.0 +requests-toolbelt==1.0.0 +rich==14.1.0 +rpds-py==0.27.1 +rsa==4.9.1 +shellingham==1.5.4 +six==1.17.0 +sniffio==1.3.1 +soupsieve==2.8 +SQLAlchemy==2.0.44 +stack-data==0.6.3 +stagehand==0.5.5 +sympy==1.14.0 +tenacity==9.1.2 +tiktoken==0.12.0 +tokenizers==0.22.1 +tomli==2.2.1 +tomli_w==1.2.0 +tqdm==4.67.1 +traitlets==5.14.3 +typer==0.19.2 +types-PyYAML==6.0.12.20250915 +types-requests==2.32.4.20250913 +typing-inspect==0.9.0 +typing-inspection==0.4.2 +typing_extensions==4.15.0 +uritemplate==4.2.0 +urllib3==2.3.0 +uv==0.8.24 +uvicorn==0.37.0 +watchfiles==1.1.0 +wcwidth==0.2.14 +websocket-client==1.8.0 +websockets==15.0.1 +yarl==1.22.0 +youtube-transcript-api==1.2.3 +zipp==3.23.0 +zstandard==0.25.0 \ No newline at end of file diff --git a/scripts/codewise_review_win.py b/scripts/codewise_review_win.py index 1506d18..8ecc642 100644 --- a/scripts/codewise_review_win.py +++ b/scripts/codewise_review_win.py @@ -133,6 +133,9 @@ def main_lint(): current_branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"], encoding='utf-8', cwd=repo_path).strip() except Exception: current_branch = "" + + # Chamando função que pergunta ao usuário se ele gostaria de continuar (enviar os dados para provedor ou não) + lgpd_check_user_choice(repo_path, current_branch) print("--- 🔍 Executando análise rápida pré-commit do CodeWise ---", file=sys.stderr) sugestoes = run_codewise_mode("lint", repo_path, current_branch) @@ -181,6 +184,9 @@ def run_pr_logic(target_selecionado, pushed_branch): upstream_existe = verificar_remote_existe('upstream', repo_path) upstream_renomeado = False + # Chamando função que pergunta ao usuário se ele gostaria de continuar (enviar os dados para provedor ou não) + lgpd_check_user_choice(repo_path, current_branch) + try: if target_selecionado == 'origin' and upstream_existe: print("Renomeando 'upstream' temporariamente para evitar bug do gh...", file=sys.stderr) @@ -372,3 +378,58 @@ def main_pr_interactive(): sys.exit(f"❌ Erro ao detectar a branch Git atual: {e}") run_pr_logic(target_selecionado=target_selecionado, pushed_branch=current_branch) + + +def lgpd_check_user_choice(repo_path:str, branch_atual:str): + caminho_dir_lgpd = os.path.join(repo_path, "analises-julgamento-lgpd") + policy_file_path = os.path.join(caminho_dir_lgpd, "analise_politica_coleta_de_dados.md") + lgpd_judge_file_path = os.path.join(caminho_dir_lgpd, "julgamento_lgpd.md") + + run_codewise_mode("lgpd_verify", repo_path, branch_atual) + + + if not os.path.exists(policy_file_path): + sys.exit("Erro: O arquivo de política de dados não existe! Execute novamente para a análise ser feita.") + + try: + with open(lgpd_judge_file_path, "r", encoding="utf-8") as f: + content_resume = f.read() + + print("-" * 40) + print() + print("Resumo sobre a análise da política de uso de dados: ") + print("") + + # Pegar apenas a conclusao do arquivo e mostrar ao usuario no cmd + conclusao = re.search(r"(?i)^#+\s*Conclusão\s*\n+(.*)", content_resume, re.MULTILINE | re.DOTALL) + + if(conclusao): + conclusao_content = conclusao.group(1).strip() + + print(conclusao_content) + print("") + + while True: + print("-" * 40) + print("\n⚠️ AVISO: Esta ação requer o envio de dados, como por exemplo, o código-fonte, para o provedor da API key fornecida.", file=sys.stderr) + print() + choice = input("* Com base na verificação apresentada acima, você gostaria de continuar com o envio de seus dados para o provedor e modelo de api key escolhido? [S/N]: ").strip().upper() + print() + if(choice == "S"): + print("-" * 40) + print("\nVocê ✅ AUTORIZOU ✅ o envio de dados necessários para o provedor da API key escolhida!", file=sys.stderr) + print("\nContinuando as análises...") + print() + print("-" * 40) + print() + return True + elif(choice == "N"): + print("-" * 40) + print("\nVocê ❌ NÃO AUTORIZOU ❌ o envio de dados necessários para o provedor da API key escolhida. Execute novamente com outro modelo ou provedor.", file=sys.stderr) + print("\nDados ❌ NÃO ENVIADOS! ❌ Interrompendo programa...", file=sys.stderr) + print() + print("-" * 40) + sys.exit(0) + except Exception as e: + print(f"Erro em obter a autorização do usuário: {e}", file=sys.stderr) + sys.exit(1) From 4281ecdf3097500ceda36ac4be6b797450393fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Ren=C3=A3?= Date: Mon, 24 Nov 2025 14:34:06 -0300 Subject: [PATCH 18/36] =?UTF-8?q?atualizando=20main=20e=20escolha=20do=20u?= =?UTF-8?q?su=C3=A1rio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codewise_lib/cw_runner.py | 205 ++++++++++++++++----------------- codewise_lib/main.py | 7 +- requirements.txt | 14 +-- scripts/codewise_review_win.py | 61 ++++++++++ 4 files changed, 170 insertions(+), 117 deletions(-) diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index c56b292..2168033 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -18,122 +18,116 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): lgpd_judge_file_path = os.path.join(caminho_dir_lgpd, "julgamento_lgpd.md") status = False - + if(self.verifica_se_existe_analise_lgpd(policy_file_path, lgpd_judge_file_path)): print("Verificando o julgamento da análise existente...") status = self.verify_result_judgement(lgpd_judge_file_path) else: print("Iniciando análise e julgamento LGPD...") status = self.verify_lgpd(caminho_repo, caminho_dir_lgpd, policy_file_path, lgpd_judge_file_path) - - if(status == True): - print("Provedor requisitado está respeitando as normas LGPD, podemos proseguir com a análise.") - print(f"Acesse os arquivos 'analise_politica_coleta_de_dados.md.' e 'julgamento_lgpd.md' para comprovações do julgamento.") - - contexto_para_ia = "" + if(modo == 'lgpd_verify'): + return 0 + + contexto_para_ia = "" - if modo == 'lint': - resultado_git = obter_mudancas_staged(caminho_repo) - - if resultado_git is None: - print("Nenhum problema aparente detectado.") - sys.exit(0) - - if resultado_git.startswith("AVISO:") or resultado_git.startswith("FALHA:"): - print(resultado_git) - sys.exit(0) - - contexto_para_ia = resultado_git - else: - if not gerar_entrada_automatica(caminho_repo, self.caminho_entrada, nome_branch): - sys.exit(0) - contexto_para_ia = self._ler_arquivo(self.caminho_entrada) + if modo == 'lint': + resultado_git = obter_mudancas_staged(caminho_repo) - codewise_instance = Codewise(commit_message=contexto_para_ia) - resultado_final = "" - - if modo == 'titulo': - agent = codewise_instance.summary_specialist() - task = Task(description=f"Crie um título de PR conciso no padrão Conventional Commits para as seguintes mudanças. A resposta deve ser APENAS o título, **obrigatoriamente em Português do Brasil**, sem aspas, acentos graves ou qualquer outro texto:\n{contexto_para_ia}", expected_output="Um único título de PR.", agent=agent) - resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() - - elif modo == 'descricao': - agent = codewise_instance.summary_specialist() - task = Task(description=f"Crie uma descrição de um parágrafo **obrigatoriamente em Português do Brasil** para um Pull Request para as seguintes mudanças:\n{contexto_para_ia}", expected_output="Um único parágrafo de texto.", agent=agent) - resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() - - elif modo == 'analise': - analysis_crew = codewise_instance.crew() - analysis_crew.kickoff(inputs={'input': contexto_para_ia}) - - print("Salvando relatórios de análise individuais...", file=sys.stderr) - - output_dir_name = "analises-concluidas" - output_dir_path = os.path.join(caminho_repo, output_dir_name) - os.makedirs(output_dir_path, exist_ok=True) - - keyword_map = { - "inspeção na estrutura do projeto": "arquitetura_atual.md", - "analise as integrações, bibliotecas externas e APIs": "analise_heuristicas_integracoes.md", - "aderência da mudança aos princípios S.O.L.I.D.": "analise_solid.md", - "aplicação correta ou ausência de padrões de projeto": "padroes_de_projeto.md" - } - - tasks_processed = {key: False for key in keyword_map} - - for task in analysis_crew.tasks: - for keyword, filename in keyword_map.items(): - if keyword in task.description and not tasks_processed[keyword]: - file_path = os.path.join(output_dir_path, filename) - try: - with open(file_path, "w", encoding="utf-8") as f: - f.write(str(task.output)) - print(f" - Arquivo '{filename}' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) - tasks_processed[keyword] = True - break - except Exception as e: - print(f" - ERRO ao salvar o arquivo '{filename}': {e}", file=sys.stderr) - - resumo_agent = codewise_instance.summary_specialist() - resumo_task = Task( - description="Com base no contexto da análise completa fornecida, crie um 'Resumo Executivo do Pull Request' **obrigatoriamente em Português do Brasil**, bem formatado em markdown, com 3-4 bullet points detalhados.", - expected_output="Um resumo executivo em markdown.", - agent=resumo_agent, - context=analysis_crew.tasks - ) - resultado_final = Crew(agents=[resumo_agent], tasks=[resumo_task]).kickoff() - - mentor_agent = codewise_instance.code_mentor() - mentor_task = Task( - description="Com base nas análises técnicas realizadas, comente e sugira recursos educacionais personalizados com base nas mudanças **obrigatoriamente em Português do Brasil**, bem formatado em markdown, com links que possuam conteúdo para melhorar o código.", - expected_output="Sugestões de melhoria.", - agent=mentor_agent, - context=analysis_crew.tasks - ) - resultado_mentor = Crew(agents=[mentor_agent],tasks=[mentor_task]).kickoff() - mentor_file_path = os.path.join(output_dir_path, "sugestoes_aprendizado.md") - try: - with open(mentor_file_path, "w", encoding="utf-8") as f: - f.write(str(resultado_mentor)) - print(f" - Arquivo 'sugestoes_aprendizado.md' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) - except Exception as e: - print(f" - ERRO ao salvar o arquivo 'sugestoes_aprendizado.md': {e}", file=sys.stderr) - - elif modo == 'lint': - agent = codewise_instance.quality_consultant() - task = Task(description=f"Analise rapidamente as seguintes mudanças de código ('git diff') e aponte APENAS problemas óbvios ou code smells. A resposta deve ser **obrigatoriamente em Português do Brasil**. Seja conciso. Se não houver problemas, retorne 'Nenhum problema aparente detectado.'.\n\nCódigo a ser analisado:\n{contexto_para_ia}", expected_output="Uma lista curta em bullet points com sugestões, ou uma mensagem de que está tudo ok.", agent=agent) - resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() + if resultado_git is None: + print("Nenhum problema aparente detectado.") + sys.exit(0) - if os.path.exists(self.caminho_entrada): - os.remove(self.caminho_entrada) + if resultado_git.startswith("AVISO:") or resultado_git.startswith("FALHA:"): + print(resultado_git) + sys.exit(0) + + contexto_para_ia = resultado_git + else: + if not gerar_entrada_automatica(caminho_repo, self.caminho_entrada, nome_branch): + sys.exit(0) + contexto_para_ia = self._ler_arquivo(self.caminho_entrada) + + codewise_instance = Codewise(commit_message=contexto_para_ia) + resultado_final = "" + + if modo == 'titulo': + agent = codewise_instance.summary_specialist() + task = Task(description=f"Crie um título de PR conciso no padrão Conventional Commits para as seguintes mudanças. A resposta deve ser APENAS o título, **obrigatoriamente em Português do Brasil**, sem aspas, acentos graves ou qualquer outro texto:\n{contexto_para_ia}", expected_output="Um único título de PR.", agent=agent) + resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() + + elif modo == 'descricao': + agent = codewise_instance.summary_specialist() + task = Task(description=f"Crie uma descrição de um parágrafo **obrigatoriamente em Português do Brasil** para um Pull Request para as seguintes mudanças:\n{contexto_para_ia}", expected_output="Um único parágrafo de texto.", agent=agent) + resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() + + elif modo == 'analise': + analysis_crew = codewise_instance.crew() + analysis_crew.kickoff(inputs={'input': contexto_para_ia}) + + print("Salvando relatórios de análise individuais...", file=sys.stderr) + + output_dir_name = "analises-concluidas" + output_dir_path = os.path.join(caminho_repo, output_dir_name) + os.makedirs(output_dir_path, exist_ok=True) + + keyword_map = { + "inspeção na estrutura do projeto": "arquitetura_atual.md", + "analise as integrações, bibliotecas externas e APIs": "analise_heuristicas_integracoes.md", + "aderência da mudança aos princípios S.O.L.I.D.": "analise_solid.md", + "aplicação correta ou ausência de padrões de projeto": "padroes_de_projeto.md" + } + + tasks_processed = {key: False for key in keyword_map} + + for task in analysis_crew.tasks: + for keyword, filename in keyword_map.items(): + if keyword in task.description and not tasks_processed[keyword]: + file_path = os.path.join(output_dir_path, filename) + try: + with open(file_path, "w", encoding="utf-8") as f: + f.write(str(task.output)) + print(f" - Arquivo '{filename}' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) + tasks_processed[keyword] = True + break + except Exception as e: + print(f" - ERRO ao salvar o arquivo '{filename}': {e}", file=sys.stderr) + + resumo_agent = codewise_instance.summary_specialist() + resumo_task = Task( + description="Com base no contexto da análise completa fornecida, crie um 'Resumo Executivo do Pull Request' **obrigatoriamente em Português do Brasil**, bem formatado em markdown, com 3-4 bullet points detalhados.", + expected_output="Um resumo executivo em markdown.", + agent=resumo_agent, + context=analysis_crew.tasks + ) + resultado_final = Crew(agents=[resumo_agent], tasks=[resumo_task]).kickoff() + + mentor_agent = codewise_instance.code_mentor() + mentor_task = Task( + description="Com base nas análises técnicas realizadas, comente e sugira recursos educacionais personalizados com base nas mudanças **obrigatoriamente em Português do Brasil**, bem formatado em markdown, com links que possuam conteúdo para melhorar o código.", + expected_output="Sugestões de melhoria.", + agent=mentor_agent, + context=analysis_crew.tasks + ) + resultado_mentor = Crew(agents=[mentor_agent],tasks=[mentor_task]).kickoff() + mentor_file_path = os.path.join(output_dir_path, "sugestoes_aprendizado.md") + try: + with open(mentor_file_path, "w", encoding="utf-8") as f: + f.write(str(resultado_mentor)) + print(f" - Arquivo 'sugestoes_aprendizado.md' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) + except Exception as e: + print(f" - ERRO ao salvar o arquivo 'sugestoes_aprendizado.md': {e}", file=sys.stderr) + + elif modo == 'lint': + agent = codewise_instance.quality_consultant() + task = Task(description=f"Analise rapidamente as seguintes mudanças de código ('git diff') e aponte APENAS problemas óbvios ou code smells. A resposta deve ser **obrigatoriamente em Português do Brasil**. Seja conciso. Se não houver problemas, retorne 'Nenhum problema aparente detectado.'.\n\nCódigo a ser analisado:\n{contexto_para_ia}", expected_output="Uma lista curta em bullet points com sugestões, ou uma mensagem de que está tudo ok.", agent=agent) + resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() + + if os.path.exists(self.caminho_entrada): + os.remove(self.caminho_entrada) - print(str(resultado_final).strip().replace('`', '')) + print(str(resultado_final).strip().replace('`', '')) - else: - print(f"Provedor requisitado não está dentro das normas LGPD! Por conta disso, as análises foram interrompidas antes que seus dados fossem enviados.") - print(f"Tente novamente escolhendo outro provedor ou modelo de api key.") - print(f"Para mais informações, acesse os arquivos 'analise_politica_coleta_de_dados.md.' e 'julgamento_lgpd.md'.") def _ler_arquivo(self, file_path: str) -> str: try: @@ -234,4 +228,5 @@ def verifica_se_existe_analise_lgpd(self, policy_file_path, lgpd_judge_file_path except Exception as e: print(f" - ERRO ao ler o arquivo 'analise_politica_coleta_de_dados.md': {e}", file=sys.stderr) else: - return False \ No newline at end of file + return False + \ No newline at end of file diff --git a/codewise_lib/main.py b/codewise_lib/main.py index 3df954b..8178db0 100644 --- a/codewise_lib/main.py +++ b/codewise_lib/main.py @@ -1,6 +1,5 @@ -import argparse -# CORREÇÃO: Adicionado o '.' para indicar uma importação relativa dentro do pacote. from .cw_runner import CodewiseRunner +import argparse def main(): parser = argparse.ArgumentParser(description="Code Wise - Ferramenta de Análise de Código com IA.") @@ -10,11 +9,11 @@ def main(): "--mode", type=str, required=True, - choices=['descricao', 'analise', 'titulo', 'lint'], + choices=['descricao', 'analise', 'titulo', 'lint', 'lgpd_verify'], help="Modo de operação." ) args = parser.parse_args() - + runner = CodewiseRunner() runner.executar( caminho_repo=args.repo, diff --git a/requirements.txt b/requirements.txt index d21a10b..a30bd96 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,6 @@ -crewai -crewai[tools] -qdrant-client -python-dotenv -pyyaml -google-generativeai -google-genai -litellm +crewai==0.201.1 +crewai-tools==0.76.0 +python-dotenv==1.1.1 +PyYAML==6.0.3 +litellm==1.74.9 +qdrant-client==1.15.1 \ No newline at end of file diff --git a/scripts/codewise_review_win.py b/scripts/codewise_review_win.py index 1506d18..8ecc642 100644 --- a/scripts/codewise_review_win.py +++ b/scripts/codewise_review_win.py @@ -133,6 +133,9 @@ def main_lint(): current_branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"], encoding='utf-8', cwd=repo_path).strip() except Exception: current_branch = "" + + # Chamando função que pergunta ao usuário se ele gostaria de continuar (enviar os dados para provedor ou não) + lgpd_check_user_choice(repo_path, current_branch) print("--- 🔍 Executando análise rápida pré-commit do CodeWise ---", file=sys.stderr) sugestoes = run_codewise_mode("lint", repo_path, current_branch) @@ -181,6 +184,9 @@ def run_pr_logic(target_selecionado, pushed_branch): upstream_existe = verificar_remote_existe('upstream', repo_path) upstream_renomeado = False + # Chamando função que pergunta ao usuário se ele gostaria de continuar (enviar os dados para provedor ou não) + lgpd_check_user_choice(repo_path, current_branch) + try: if target_selecionado == 'origin' and upstream_existe: print("Renomeando 'upstream' temporariamente para evitar bug do gh...", file=sys.stderr) @@ -372,3 +378,58 @@ def main_pr_interactive(): sys.exit(f"❌ Erro ao detectar a branch Git atual: {e}") run_pr_logic(target_selecionado=target_selecionado, pushed_branch=current_branch) + + +def lgpd_check_user_choice(repo_path:str, branch_atual:str): + caminho_dir_lgpd = os.path.join(repo_path, "analises-julgamento-lgpd") + policy_file_path = os.path.join(caminho_dir_lgpd, "analise_politica_coleta_de_dados.md") + lgpd_judge_file_path = os.path.join(caminho_dir_lgpd, "julgamento_lgpd.md") + + run_codewise_mode("lgpd_verify", repo_path, branch_atual) + + + if not os.path.exists(policy_file_path): + sys.exit("Erro: O arquivo de política de dados não existe! Execute novamente para a análise ser feita.") + + try: + with open(lgpd_judge_file_path, "r", encoding="utf-8") as f: + content_resume = f.read() + + print("-" * 40) + print() + print("Resumo sobre a análise da política de uso de dados: ") + print("") + + # Pegar apenas a conclusao do arquivo e mostrar ao usuario no cmd + conclusao = re.search(r"(?i)^#+\s*Conclusão\s*\n+(.*)", content_resume, re.MULTILINE | re.DOTALL) + + if(conclusao): + conclusao_content = conclusao.group(1).strip() + + print(conclusao_content) + print("") + + while True: + print("-" * 40) + print("\n⚠️ AVISO: Esta ação requer o envio de dados, como por exemplo, o código-fonte, para o provedor da API key fornecida.", file=sys.stderr) + print() + choice = input("* Com base na verificação apresentada acima, você gostaria de continuar com o envio de seus dados para o provedor e modelo de api key escolhido? [S/N]: ").strip().upper() + print() + if(choice == "S"): + print("-" * 40) + print("\nVocê ✅ AUTORIZOU ✅ o envio de dados necessários para o provedor da API key escolhida!", file=sys.stderr) + print("\nContinuando as análises...") + print() + print("-" * 40) + print() + return True + elif(choice == "N"): + print("-" * 40) + print("\nVocê ❌ NÃO AUTORIZOU ❌ o envio de dados necessários para o provedor da API key escolhida. Execute novamente com outro modelo ou provedor.", file=sys.stderr) + print("\nDados ❌ NÃO ENVIADOS! ❌ Interrompendo programa...", file=sys.stderr) + print() + print("-" * 40) + sys.exit(0) + except Exception as e: + print(f"Erro em obter a autorização do usuário: {e}", file=sys.stderr) + sys.exit(1) From f7b73a60cf60e545ac5a388798ba4f294a58d1c9 Mon Sep 17 00:00:00 2001 From: Gustavo Saraiva Date: Mon, 24 Nov 2025 14:36:52 -0300 Subject: [PATCH 19/36] =?UTF-8?q?corre=C3=A7=C3=A3o=20das=20vers=C3=B5es?= =?UTF-8?q?=20do=20crewAI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/requirements.txt b/requirements.txt index f0d264e..a30bd96 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,6 @@ -crewai -crewai[tools] -qdrant-client -python-dotenv -pyyaml -google-generativeai -google-genai -langchain-groq -groq -langchain-openai -openai -langchain-cohere -cohere -litellm +crewai==0.201.1 +crewai-tools==0.76.0 +python-dotenv==1.1.1 +PyYAML==6.0.3 +litellm==1.74.9 +qdrant-client==1.15.1 \ No newline at end of file From 4985a2531bc8eb6a60a6309972c68ec1848b7563 Mon Sep 17 00:00:00 2001 From: Matheus Magnago Date: Thu, 27 Nov 2025 18:53:14 -0300 Subject: [PATCH 20/36] lgpd user choice input working --- codewise_lib/config/tasks.yaml | 2 +- codewise_lib/cw_runner.py | 22 +++++++++++----------- scripts/codewise_review_win.py | 32 +++++++++++++++++++++++++++----- 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/codewise_lib/config/tasks.yaml b/codewise_lib/config/tasks.yaml index 5a41872..c47e7ff 100644 --- a/codewise_lib/config/tasks.yaml +++ b/codewise_lib/config/tasks.yaml @@ -59,7 +59,7 @@ lgpd_judging: description: > Com base nas leis LGPD e no relatório da analise da documentação, determine se o provedor da api key está respeitando ou não as leis LGPD e justifique o julgamento apontando as provas, por meio de um relatório, onde a primeira linha **exatamente na linha 1** do arquivo é a resposta antecipada (sim ou não). - Lembre-se de usar a ferramenta para buscar as leis LGPD do site oficial (gov.br). + Lembre-se de usar a ferramenta para buscar as leis LGPD do site oficial (gov.br). Além disso, coloque a conclusão sempre no final do arquivo. expected_output: > Documento julgamento_lgpd.md com as provas ou artigos utilizados para determinar a decisão tomada. agent: lgpd_judge \ No newline at end of file diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index 2168033..c42f57a 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -17,17 +17,18 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): policy_file_path = os.path.join(caminho_dir_lgpd, "analise_politica_coleta_de_dados.md") lgpd_judge_file_path = os.path.join(caminho_dir_lgpd, "julgamento_lgpd.md") - status = False - - if(self.verifica_se_existe_analise_lgpd(policy_file_path, lgpd_judge_file_path)): - print("Verificando o julgamento da análise existente...") - status = self.verify_result_judgement(lgpd_judge_file_path) - else: - print("Iniciando análise e julgamento LGPD...") - status = self.verify_lgpd(caminho_repo, caminho_dir_lgpd, policy_file_path, lgpd_judge_file_path) - if(modo == 'lgpd_verify'): + if(self.verifica_se_existe_analise_lgpd(policy_file_path, lgpd_judge_file_path)): + print("\nA análise e verificação LGPD já foi feita anteriormente para este mesmo provedor e modelo api key. Por conta disso, não será necessário efetuá-la novamente! \n",file=sys.stderr) + print("Verificando o julgamento da análise existente...\n" ,file=sys.stderr) + self.verify_result_judgement(lgpd_judge_file_path) + else: + print("Iniciando análise e julgamento LGPD...",file=sys.stderr) + self.verify_lgpd(caminho_repo, caminho_dir_lgpd, policy_file_path, lgpd_judge_file_path) return 0 + + #if(modo == 'lgpd_verify'): + # return 0 contexto_para_ia = "" @@ -35,7 +36,7 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): resultado_git = obter_mudancas_staged(caminho_repo) if resultado_git is None: - print("Nenhum problema aparente detectado.") + print("Nenhum problema aparente detectado.",file=sys.stderr) sys.exit(0) if resultado_git.startswith("AVISO:") or resultado_git.startswith("FALHA:"): @@ -221,7 +222,6 @@ def verifica_se_existe_analise_lgpd(self, policy_file_path, lgpd_judge_file_path linha_clean = linha_clean.strip() if(linha_clean == provider_model): - print("A análise e verificação LGPD já foi feita anteriormente para este mesmo provedor e modelo api key. Por conta disso, não será necessário efetuá-la novamente!") return True if(not linha): return False diff --git a/scripts/codewise_review_win.py b/scripts/codewise_review_win.py index 8ecc642..311a659 100644 --- a/scripts/codewise_review_win.py +++ b/scripts/codewise_review_win.py @@ -36,6 +36,10 @@ def run_codewise_mode(mode, repo_path, branch_name): env=env, stdin=subprocess.DEVNULL ) + + #if result.stderr: + # print(result.stderr, file=sys.stderr) + return result.stdout.strip() except subprocess.CalledProcessError as e: error_output = e.stderr or "" @@ -166,7 +170,6 @@ def main_lint(): def run_pr_logic(target_selecionado, pushed_branch): """Função principal que contém toda a lógica de criação de PR.""" - current_branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"], text=True).strip() if current_branch != pushed_branch: @@ -181,12 +184,12 @@ def run_pr_logic(target_selecionado, pushed_branch): os.environ['PYTHONIOENCODING'] = 'utf-8' repo_path = os.getcwd() - upstream_existe = verificar_remote_existe('upstream', repo_path) - upstream_renomeado = False - # Chamando função que pergunta ao usuário se ele gostaria de continuar (enviar os dados para provedor ou não) lgpd_check_user_choice(repo_path, current_branch) + upstream_existe = verificar_remote_existe('upstream', repo_path) + upstream_renomeado = False + try: if target_selecionado == 'origin' and upstream_existe: print("Renomeando 'upstream' temporariamente para evitar bug do gh...", file=sys.stderr) @@ -413,7 +416,26 @@ def lgpd_check_user_choice(repo_path:str, branch_atual:str): print("-" * 40) print("\n⚠️ AVISO: Esta ação requer o envio de dados, como por exemplo, o código-fonte, para o provedor da API key fornecida.", file=sys.stderr) print() - choice = input("* Com base na verificação apresentada acima, você gostaria de continuar com o envio de seus dados para o provedor e modelo de api key escolhido? [S/N]: ").strip().upper() + + # Trecho utilizando biblioteca para receber o input direto do SO! + try: + # Se estiver no Windows (seu caso) + if sys.platform == 'win32': + import msvcrt + + print("\nCom base na verificação apresentada acima, você gostaria de continuar com o envio de seus dados para o provedor e modelo de api key escolhido? [S/N]:") + char = msvcrt.getwche() + choice = char.upper() + print() + + # Se estiver no Linux/Mac (Fallback para compatibilidade) + else: + with open("/dev/tty", "r") as tty: + choice = tty.readline().strip().upper() + except Exception: + choice = input().strip().upper() + + #choice = input("* Com base na verificação apresentada acima, você gostaria de continuar com o envio de seus dados para o provedor e modelo de api key escolhido? [S/N]: ").strip().upper() print() if(choice == "S"): print("-" * 40) From 18793041205e16d0c19f2e64a55b121013a4fd5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Ren=C3=A3?= Date: Sun, 30 Nov 2025 16:23:35 -0300 Subject: [PATCH 21/36] adicionando agente e tool de score - pr com algum bug --- codewise_lib/code_reviewer.py | 200 +++++++++++++++++++++ codewise_lib/config/agents.yaml | 7 +- codewise_lib/config/tasks.yaml | 33 +++- codewise_lib/crew.py | 7 + codewise_lib/cw_runner.py | 33 +++- codewise_lib/tools/__init__.py | 3 + codewise_lib/tools/git_analysis_tool.py | 230 ++++++++++++++++++++++++ 7 files changed, 509 insertions(+), 4 deletions(-) create mode 100644 codewise_lib/code_reviewer.py create mode 100644 codewise_lib/tools/__init__.py create mode 100644 codewise_lib/tools/git_analysis_tool.py diff --git a/codewise_lib/code_reviewer.py b/codewise_lib/code_reviewer.py new file mode 100644 index 0000000..1a421cc --- /dev/null +++ b/codewise_lib/code_reviewer.py @@ -0,0 +1,200 @@ +import os +import sys +import yaml +import tempfile +from datetime import datetime +from dotenv import load_dotenv +from crewai import Agent, Crew, Process, Task +from .select_llm import create_llm +from .tools.git_analysis_tool import GitAnalysisTool, GitBlameAnalysisTool + + +class CodeReviewerCrew: + """Crew especializada em avaliar mudanças de código e gerar notas.""" + + def __init__(self, repo_path: str, author_email: str = None, commits_limit: int = 5): + load_dotenv() + self.repo_path = repo_path + self.author_email = author_email + self.commits_limit = commits_limit + + provider = os.getenv("AI_PROVIDER").upper() + model = os.getenv("AI_MODEL") + self.llm = create_llm(provider, model) + + # Ferramentas Git + self.git_tool = GitAnalysisTool() + self.git_blame_tool = GitBlameAnalysisTool() + + # Carrega configurações + base_dir = os.path.dirname(os.path.abspath(__file__)) + config_path = os.path.join(base_dir, "config") + agents_path = os.path.join(config_path, "agents.yaml") + tasks_path = os.path.join(config_path, "tasks.yaml") + + with open(agents_path, "r", encoding="utf-8") as f: + self.agents_config = yaml.safe_load(f) + with open(tasks_path, "r", encoding="utf-8") as f: + self.tasks_config = yaml.safe_load(f) + + def analyze_git_changes(self) -> str: + """Coleta dados do Git para análise.""" + print(f"Analisando mudanças no repositório: {self.repo_path}") + + git_data = self.git_tool.run( + repo_path=self.repo_path, + author_email=self.author_email, + commits_limit=self.commits_limit, + branch="HEAD" + ) + + return git_data + + def create_reviewer_agent(self) -> Agent: + """Cria o agente revisor de código.""" + return Agent( + config=self.agents_config['code_reviewer'], + llm=self.llm, + verbose=True + ) + + def create_review_task(self, git_analysis_data: str) -> Task: + """Cria a task de revisão de código.""" + cfg = self.tasks_config['code_review_scoring'] + + # Substitui placeholders + description = cfg['description'].format( + git_analysis_data=git_analysis_data + ) + + # Determina o nome do arquivo de saída + author_identifier = self.author_email.replace('@', '_').replace('.', '_') if self.author_email else "geral" + expected_output = cfg['expected_output'].format( + author_email=author_identifier + ) + + return Task( + description=description, + expected_output=expected_output, + agent=self.create_reviewer_agent() + ) + + def run_review(self, output_dir: str = None) -> str: + """Executa a revisão completa e gera o arquivo de avaliação.""" + print("Iniciando revisão de código...") + + #Coleta dados do Git + git_data = self.analyze_git_changes() + + if "Erro" in git_data: + print(f"{git_data}") + return None + + #Salva dados em arquivo temporário para referência + temp_file = tempfile.NamedTemporaryFile( + mode='w', + delete=False, + suffix='.txt', + encoding='utf-8' + ) + temp_file.write(git_data) + temp_file.close() + + print(f"Dados Git salvos em: {temp_file.name}") + + #Cria crew de revisão + reviewer = self.create_reviewer_agent() + review_task = self.create_review_task(git_data) + + crew = Crew( + agents=[reviewer], + tasks=[review_task], + process=Process.sequential, + verbose=True + ) + + # Executa a revisão + print("Executando análise de código...") + result = crew.kickoff() + + # Salva resultado + if output_dir is None: + output_dir = os.path.join(self.repo_path, 'code_reviews') + + os.makedirs(output_dir, exist_ok=True) + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + author_identifier = self.author_email.replace('@', '_').replace('.', '_') if self.author_email else "geral" + output_file = os.path.join( + output_dir, + f"avaliacao_codigo_{author_identifier}_{timestamp}.md" + ) + + with open(output_file, 'w', encoding='utf-8') as f: + f.write(f"# Avaliação de Código\n\n") + f.write(f"**Data da Análise:** {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}\n\n") + if self.author_email: + f.write(f"**Desenvolvedor:** {self.author_email}\n\n") + f.write("---\n\n") + f.write(str(result)) + + print(f"Avaliação concluída e salva em: {output_file}") + + # Limpa arquivo temporário + try: + os.unlink(temp_file.name) + except: + pass + + return output_file + + +def main(): + """Função principal para uso via CLI.""" + import argparse + + parser = argparse.ArgumentParser( + description="Code Reviewer - Avalia mudanças de código e gera notas" + ) + parser.add_argument( + "--repo", + type=str, + required=True, + help="Caminho para o repositório Git" + ) + parser.add_argument( + "--author", + type=str, + help="Email do autor para filtrar commits (opcional)" + ) + parser.add_argument( + "--commits", + type=int, + default=5, + help="Número de commits a analisar (padrão: 5)" + ) + parser.add_argument( + "--output", + type=str, + help="Diretório de saída para o arquivo de avaliação (opcional)" + ) + + args = parser.parse_args() + + reviewer = CodeReviewerCrew( + repo_path=args.repo, + author_email=args.author, + commits_limit=args.commits + ) + + output_file = reviewer.run_review(output_dir=args.output) + + if output_file: + print(f"\nRevisão completa! Arquivo gerado: {output_file}") + else: + print("\nFalha na revisão de código.") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/codewise_lib/config/agents.yaml b/codewise_lib/config/agents.yaml index 3d08e02..f4b2d37 100644 --- a/codewise_lib/config/agents.yaml +++ b/codewise_lib/config/agents.yaml @@ -38,4 +38,9 @@ dataCollect_policy_analytics: lgpd_judge: role: Especialista em leis LGPD goal: Comparar o resumo da política de coleta de dados de um provedor de api key com as leis LGPD e determinar se tal provedor está respeitando as leis LGPD. - backstory: Um especialista em leis LGPD que julga se há o cumprimento dessas leis por parte do provedor da api key. \ No newline at end of file + backstory: Um especialista em leis LGPD que julga se há o cumprimento dessas leis por parte do provedor da api key. + +code_reviewer: + role: Revisor Sênior de Código e Avaliador de Qualidade + goal: Analisar mudanças de código realizadas por desenvolvedores e atribuir uma nota objetiva baseada em critérios técnicos rigorosos de qualidade, arquitetura, boas práticas e impacto. + backstory: Um engenheiro de software altamente experiente com mais de 15 anos de experiência em revisão de código, arquitetura de sistemas e mentoria técnica. Especialista em identificar problemas sutis de design, violações de princípios SOLID, code smells, problemas de performance e segurança. Conhecido por avaliações justas, objetivas e fundamentadas tecnicamente. Utiliza uma abordagem holística considerando não apenas o código em si, mas também o contexto, complexidade, impacto e aderência às melhores práticas da indústria. \ No newline at end of file diff --git a/codewise_lib/config/tasks.yaml b/codewise_lib/config/tasks.yaml index c47e7ff..5678d33 100644 --- a/codewise_lib/config/tasks.yaml +++ b/codewise_lib/config/tasks.yaml @@ -62,4 +62,35 @@ lgpd_judging: Lembre-se de usar a ferramenta para buscar as leis LGPD do site oficial (gov.br). Além disso, coloque a conclusão sempre no final do arquivo. expected_output: > Documento julgamento_lgpd.md com as provas ou artigos utilizados para determinar a decisão tomada. - agent: lgpd_judge \ No newline at end of file + agent: lgpd_judge + +code_review_scoring: + description: > + Analise as mudanças de código do desenvolvedor com base nos dados fornecidos: "{git_analysis_data}". + + Avalie criteriosamente os seguintes aspectos: + 1. Qualidade do Código (0-2.5 pontos): Legibilidade, clareza, nomenclatura, organização + 2. Arquitetura e Design (0-2.5 pontos): Aderência a SOLID, padrões de projeto, separação de responsabilidades + 3. Boas Práticas (0-2.5 pontos): Tratamento de erros, segurança, performance, testes + 4. Impacto e Complexidade (0-2.5 pontos): Tamanho da mudança, complexidade ciclomática, risco de bugs + + Considere também: + - Consistência com o código existente + - Documentação e comentários quando necessários + - Potenciais problemas de manutenibilidade + - Code smells e anti-patterns + - Eficiência e otimização + + Seja rigoroso mas justo. Uma nota 10 deve ser excepcional, não comum. + Notas entre 7-8.5 são boas, 8.5-9.5 são muito boas, 9.5-10 são excepcionais. + Abaixo de 7 indica necessidade de melhorias significativas. + expected_output: > + Documento avaliacao_codigo_{author_email}.md contendo: + - Nome do desenvolvedor e email + - Nota final (0-10) + - Breakdown de pontos por categoria + - Justificativa detalhada e técnica para a nota + - Pontos fortes identificados + - Pontos de melhoria com exemplos específicos + - Recomendações práticas e acionáveis + agent: code_reviewer \ No newline at end of file diff --git a/codewise_lib/crew.py b/codewise_lib/crew.py index 1a35dd8..4d0162c 100644 --- a/codewise_lib/crew.py +++ b/codewise_lib/crew.py @@ -11,6 +11,8 @@ WebsiteSearchTool ) +from .tools.git_analysis_tool import GitAnalysisTool, GitBlameAnalysisTool + @CrewBase class Codewise: """Classe principal da crew Codewise""" @@ -25,6 +27,8 @@ def __init__(self, commit_message: str = ""): #tools self.web_search_tool = WebsiteSearchTool() + self.git_analysis_tool = GitAnalysisTool() + self.git_blame_tool = GitBlameAnalysisTool() base_dir = os.path.dirname(os.path.abspath(__file__)) config_path = os.path.join(base_dir, "config") @@ -59,6 +63,9 @@ def dataCollect_policy_analytics(self) -> Agent: return Agent(config=self.agents @agent def lgpd_judge(self) -> Agent: return Agent(config=self.agents_config['lgpd_judge'], llm=self.llm, tools=[self.web_search_tool], verbose = False) + @agent + def code_reviewer(self) -> Agent: return Agent(config=self.agents_config['code_reviewer'], llm=self.llm, verbose=True) + @task def task_estrutura(self) -> Task: cfg = self.tasks_config['analise_estrutura'] diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index c42f57a..8677d65 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -4,6 +4,7 @@ from .crew import Codewise from .entradagit import gerar_entrada_automatica, obter_mudancas_staged from crewai import Task, Crew +from .code_reviewer import CodeReviewerCrew class CodewiseRunner: @@ -27,8 +28,7 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): self.verify_lgpd(caminho_repo, caminho_dir_lgpd, policy_file_path, lgpd_judge_file_path) return 0 - #if(modo == 'lgpd_verify'): - # return 0 + contexto_para_ia = "" @@ -118,6 +118,35 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): print(f" - Arquivo 'sugestoes_aprendizado.md' salvo com sucesso em '{output_dir_path}'.", file=sys.stderr) except Exception as e: print(f" - ERRO ao salvar o arquivo 'sugestoes_aprendizado.md': {e}", file=sys.stderr) + + # Executa Code Review automaticamente após a análise + print("\n🔍 Gerando avaliação de código...", file=sys.stderr) + try: + import subprocess + try: + result = subprocess.run( + ['git', '-C', caminho_repo, 'config', 'user.email'], + capture_output=True, + text=True, + check=True + ) + user_email = result.stdout.strip() + except: + user_email = None + + reviewer = CodeReviewerCrew( + repo_path=caminho_repo, + author_email=user_email, + commits_limit=3 + ) + + output_file = reviewer.run_review(output_dir=output_dir_path) + + if output_file: + print(f" - Arquivo de avaliação salvo: {os.path.basename(output_file)}", file=sys.stderr) + + except Exception as e: + print(f" - Aviso: Não foi possível gerar avaliação de código: {str(e)}", file=sys.stderr) elif modo == 'lint': agent = codewise_instance.quality_consultant() diff --git a/codewise_lib/tools/__init__.py b/codewise_lib/tools/__init__.py new file mode 100644 index 0000000..dc35b78 --- /dev/null +++ b/codewise_lib/tools/__init__.py @@ -0,0 +1,3 @@ +from .git_analysis_tool import GitAnalysisTool, GitBlameAnalysisTool + +__all__ = ['GitAnalysisTool', 'GitBlameAnalysisTool'] diff --git a/codewise_lib/tools/git_analysis_tool.py b/codewise_lib/tools/git_analysis_tool.py new file mode 100644 index 0000000..719832b --- /dev/null +++ b/codewise_lib/tools/git_analysis_tool.py @@ -0,0 +1,230 @@ +import os +import subprocess +import tempfile +from typing import Optional + + +class GitAnalysisTool: + """Ferramenta para analisar mudanças do Git usando git log e git blame.""" + + name: str = "Git Analysis Tool" + description: str = ( + "Ferramenta para analisar mudanças do Git usando git log e git blame. " + "Retorna informações detalhadas sobre commits, autores, e mudanças no código." + ) + + def run(self, repo_path: str, author_email: Optional[str] = None, + commits_limit: int = 10, branch: str = "HEAD") -> str: + """ + Analisa o repositório Git e retorna informações sobre commits e mudanças. + + Args: + repo_path: Caminho para o repositório Git + author_email: Email do autor para filtrar (opcional) + commits_limit: Número de commits a analisar + branch: Branch a ser analisada + """ + try: + # Verifica se é um repositório Git válido + if not os.path.exists(os.path.join(repo_path, '.git')): + return f"Erro: {repo_path} não é um repositório Git válido." + + result = [] + result.append("=" * 80) + result.append("ANÁLISE DE MUDANÇAS GIT") + result.append("=" * 80) + result.append("") + + # Comando git log com informações detalhadas + git_log_cmd = [ + 'git', '-C', repo_path, 'log', + f'-{commits_limit}', + '--pretty=format:%H|%an|%ae|%ad|%s', + '--date=iso', + '--numstat', + branch + ] + + if author_email: + git_log_cmd.extend(['--author', author_email]) + + log_output = subprocess.check_output( + git_log_cmd, + stderr=subprocess.STDOUT, + text=True + ) + + # Processa a saída do git log + commits_data = self._parse_git_log(log_output) + + for commit in commits_data: + result.append(f"Commit: {commit['hash'][:8]}") + result.append(f"Autor: {commit['author']} <{commit['email']}>") + result.append(f"Data: {commit['date']}") + result.append(f"Mensagem: {commit['message']}") + result.append("") + + if commit['files']: + result.append("Arquivos modificados:") + for file_info in commit['files']: + result.append(f" {file_info['additions']:>4}+ {file_info['deletions']:>4}- {file_info['file']}") + result.append("") + + # Git diff para ver as mudanças reais + try: + diff_cmd = ['git', '-C', repo_path, 'show', + '--pretty=format:', '--unified=3', commit['hash']] + diff_output = subprocess.check_output( + diff_cmd, + stderr=subprocess.STDOUT, + text=True + ) + if diff_output.strip(): + result.append("Mudanças no código:") + result.append(diff_output[:2000]) # Limita o tamanho + result.append("") + except: + pass + + result.append("-" * 80) + result.append("") + + return "\n".join(result) + + except subprocess.CalledProcessError as e: + return f"Erro ao executar comando Git: {e.output}" + except Exception as e: + return f"Erro na análise Git: {str(e)}" + + def _parse_git_log(self, log_output: str) -> list: + """Parse da saída do git log.""" + commits = [] + current_commit = None + + for line in log_output.split('\n'): + if '|' in line and len(line.split('|')) == 5: + # Nova linha de commit + if current_commit: + commits.append(current_commit) + + parts = line.split('|') + current_commit = { + 'hash': parts[0], + 'author': parts[1], + 'email': parts[2], + 'date': parts[3], + 'message': parts[4], + 'files': [] + } + elif line.strip() and current_commit and '\t' in line: + # Linha de estatísticas de arquivo + parts = line.split('\t') + if len(parts) == 3: + additions = parts[0] if parts[0] != '-' else '0' + deletions = parts[1] if parts[1] != '-' else '0' + current_commit['files'].append({ + 'additions': additions, + 'deletions': deletions, + 'file': parts[2] + }) + + if current_commit: + commits.append(current_commit) + + return commits + + +class GitBlameAnalysisTool: + """Ferramenta para analisar autoria de linhas de código usando git blame.""" + + name: str = "Git Blame Analysis Tool" + description: str = ( + "Ferramenta para analisar autoria de linhas de código usando git blame. " + "Útil para entender quem modificou cada parte do código." + ) + + def run(self, repo_path: str, file_path: str) -> str: + """ + Analisa a autoria de um arquivo específico usando git blame. + + Args: + repo_path: Caminho para o repositório Git + file_path: Caminho relativo do arquivo a ser analisado + """ + try: + full_path = os.path.join(repo_path, file_path) + + if not os.path.exists(full_path): + return f"Erro: Arquivo {file_path} não encontrado." + + # Git blame com informações detalhadas + blame_cmd = [ + 'git', '-C', repo_path, 'blame', + '--line-porcelain', + file_path + ] + + blame_output = subprocess.check_output( + blame_cmd, + stderr=subprocess.STDOUT, + text=True + ) + + # Processa e agrupa por autor + authors_stats = self._parse_git_blame(blame_output) + + result = [] + result.append("=" * 80) + result.append(f"ANÁLISE DE AUTORIA: {file_path}") + result.append("=" * 80) + result.append("") + + for author, stats in sorted(authors_stats.items(), + key=lambda x: x[1]['lines'], + reverse=True): + percentage = (stats['lines'] / stats['total_lines']) * 100 + result.append(f"Autor: {author}") + result.append(f"Email: {stats['email']}") + result.append(f"Linhas: {stats['lines']} ({percentage:.1f}%)") + result.append(f"Último commit: {stats['last_commit'][:8]}") + result.append("") + + return "\n".join(result) + + except subprocess.CalledProcessError as e: + return f"Erro ao executar git blame: {e.output}" + except Exception as e: + return f"Erro na análise: {str(e)}" + + def _parse_git_blame(self, blame_output: str) -> dict: + """Parse da saída do git blame.""" + authors = {} + current_commit = None + current_author = None + current_email = None + total_lines = 0 + + for line in blame_output.split('\n'): + if line.startswith('author '): + current_author = line[7:] + elif line.startswith('author-mail '): + current_email = line[12:].strip('<>') + elif line.startswith('summary '): + if current_author: + if current_author not in authors: + authors[current_author] = { + 'email': current_email, + 'lines': 0, + 'last_commit': current_commit, + 'total_lines': 0 + } + authors[current_author]['lines'] += 1 + total_lines += 1 + elif len(line) == 40 and all(c in '0123456789abcdef' for c in line): + current_commit = line + + # Atualiza total de linhas + for author in authors: + authors[author]['total_lines'] = total_lines + + return authors From 99421b7f448e59c0d4d37de6fd67cf34481d1f8c Mon Sep 17 00:00:00 2001 From: Matheus Magnago Date: Sun, 30 Nov 2025 16:44:28 -0300 Subject: [PATCH 22/36] Creating an lgpd.py file for modularization --- codewise_lib/cw_runner.py | 103 +------------------ codewise_lib/lgpd.py | 100 ++++++++++++++++++ requirements.txt | 208 -------------------------------------- 3 files changed, 104 insertions(+), 307 deletions(-) create mode 100644 codewise_lib/lgpd.py diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index c42f57a..3b3281c 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -4,6 +4,7 @@ from .crew import Codewise from .entradagit import gerar_entrada_automatica, obter_mudancas_staged from crewai import Task, Crew +from .lgpd import * class CodewiseRunner: @@ -18,13 +19,13 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): lgpd_judge_file_path = os.path.join(caminho_dir_lgpd, "julgamento_lgpd.md") if(modo == 'lgpd_verify'): - if(self.verifica_se_existe_analise_lgpd(policy_file_path, lgpd_judge_file_path)): + if(verifica_se_existe_analise_lgpd(policy_file_path, lgpd_judge_file_path)): print("\nA análise e verificação LGPD já foi feita anteriormente para este mesmo provedor e modelo api key. Por conta disso, não será necessário efetuá-la novamente! \n",file=sys.stderr) print("Verificando o julgamento da análise existente...\n" ,file=sys.stderr) - self.verify_result_judgement(lgpd_judge_file_path) + verify_result_judgement(lgpd_judge_file_path) else: print("Iniciando análise e julgamento LGPD...",file=sys.stderr) - self.verify_lgpd(caminho_repo, caminho_dir_lgpd, policy_file_path, lgpd_judge_file_path) + verify_lgpd(caminho_repo, caminho_dir_lgpd, policy_file_path, lgpd_judge_file_path) return 0 #if(modo == 'lgpd_verify'): @@ -134,99 +135,3 @@ def _ler_arquivo(self, file_path: str) -> str: try: with open(file_path, "r", encoding="utf-8") as f: return f.read() except FileNotFoundError: return "" - - def verify_lgpd(self, caminho_repo: str, caminho_dir_lgpd: str, policy_file_path: str, lgpd_judge_file_path: str) -> bool: - # Tentativa de colocar a analise lgpd para rodar antes do envio dos dados sensiveis - # instancia sem passar o commit como contexto - codewise_instance = Codewise() - - print(f"Verificando a política de coleta de dados do provedor com base neste modelo de api key...") - - #lgpd crew - lgpd_check_crew = codewise_instance.lgpd_crew() - - # roda a analise e o julgamento lgpd (todo o time) - lgpd_check_crew.kickoff() - - # cria directory - os.makedirs(caminho_dir_lgpd, exist_ok=True) - - # salvando o resultado da analise da politica de coleta de dados - resultado_policy = lgpd_check_crew.tasks[0].output - - # definindo caminho e nome do arquivo final. - #policy_file_path = os.path.join(output_dir_path, "analise_politica_coleta_de_dados.md") - - try: - with open(policy_file_path, "w", encoding="utf-8") as f: - f.write(str(resultado_policy)) - print(f" - Arquivo 'analise_politica_coleta_de_dados.md' salvo com sucesso em '{caminho_dir_lgpd}'.", file=sys.stderr) - except Exception as e: - print(f" - ERRO ao salvar o arquivo 'analise_politica_coleta_de_dados.md': {e}", file=sys.stderr) - - - #salvando o julgamento lgpd - resultado_lgpd = lgpd_check_crew.tasks[1].output - - # definindo caminho e nome do arquivo final. - #lgpd_judge_file_path = os.path.join(output_dir_path, "julgamento_lgpd.md") - - try: - with open(lgpd_judge_file_path, "w", encoding="utf-8") as fj: - fj.write(str(resultado_lgpd)) - print(f" - Arquivo 'julgamento_lgpd.md' salvo com sucesso em '{caminho_dir_lgpd}'.", file=sys.stderr) - except Exception as e: - print(f" - ERRO ao salvar o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) - - # fim - - # Verificação dos resultados da política de coleta de dados do provedor e model utilizados - return self.verify_result_judgement(lgpd_judge_file_path) - - def verify_result_judgement(self, lgpd_judge_file_path) -> bool: - try: - with open(lgpd_judge_file_path, "r", encoding="utf-8") as julgamento: - #print(julgamento.readline()) - - for linha in julgamento: - linha_clean = linha.strip().lower() - - linha_clean = re.sub(r'[*_#>`~]', '', linha_clean).strip() - - linha_clean = linha_clean.strip() - - if(linha_clean == "sim"): - return True - if (linha_clean == "não"): - return False - except Exception as e: - print(f" - ERRO ao ler o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) - - def verifica_se_existe_analise_lgpd(self, policy_file_path, lgpd_judge_file_path) -> bool: - - provider = os.getenv("AI_PROVIDER").lower() - model = os.getenv("AI_MODEL").lower() - model = re.sub(r'[*_#>`~]', '', model) - - provider_model = provider + model - - if(os.path.exists(policy_file_path) and os.path.exists(lgpd_judge_file_path)): - try: - with open(policy_file_path, "r", encoding="utf-8") as f: - #print(julgamento.readline()) - - for linha in f: - linha_clean = linha.strip().lower() - - linha_clean = re.sub(r'[*_#>`~]', '', linha_clean).strip() - - linha_clean = linha_clean.strip() - if(linha_clean == provider_model): - return True - if(not linha): - return False - except Exception as e: - print(f" - ERRO ao ler o arquivo 'analise_politica_coleta_de_dados.md': {e}", file=sys.stderr) - else: - return False - \ No newline at end of file diff --git a/codewise_lib/lgpd.py b/codewise_lib/lgpd.py new file mode 100644 index 0000000..ed1beab --- /dev/null +++ b/codewise_lib/lgpd.py @@ -0,0 +1,100 @@ +import os +import sys +import re +from dotenv import load_dotenv +from .crew import Codewise + +def verify_lgpd(caminho_repo: str, caminho_dir_lgpd: str, policy_file_path: str, lgpd_judge_file_path: str) -> bool: + # Tentativa de colocar a analise lgpd para rodar antes do envio dos dados sensiveis + # instancia sem passar o commit como contexto + codewise_instance = Codewise() + + print(f"Verificando a política de coleta de dados do provedor com base neste modelo de api key...") + + #lgpd crew + lgpd_check_crew = codewise_instance.lgpd_crew() + + # roda a analise e o julgamento lgpd (todo o time) + lgpd_check_crew.kickoff() + + # cria directory + os.makedirs(caminho_dir_lgpd, exist_ok=True) + + # salvando o resultado da analise da politica de coleta de dados + resultado_policy = lgpd_check_crew.tasks[0].output + + # definindo caminho e nome do arquivo final. + #policy_file_path = os.path.join(output_dir_path, "analise_politica_coleta_de_dados.md") + + try: + with open(policy_file_path, "w", encoding="utf-8") as f: + f.write(str(resultado_policy)) + print(f" - Arquivo 'analise_politica_coleta_de_dados.md' salvo com sucesso em '{caminho_dir_lgpd}'.", file=sys.stderr) + except Exception as e: + print(f" - ERRO ao salvar o arquivo 'analise_politica_coleta_de_dados.md': {e}", file=sys.stderr) + + + #salvando o julgamento lgpd + resultado_lgpd = lgpd_check_crew.tasks[1].output + + # definindo caminho e nome do arquivo final. + #lgpd_judge_file_path = os.path.join(output_dir_path, "julgamento_lgpd.md") + + try: + with open(lgpd_judge_file_path, "w", encoding="utf-8") as fj: + fj.write(str(resultado_lgpd)) + print(f" - Arquivo 'julgamento_lgpd.md' salvo com sucesso em '{caminho_dir_lgpd}'.", file=sys.stderr) + except Exception as e: + print(f" - ERRO ao salvar o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) + + # fim + + # Verificação dos resultados da política de coleta de dados do provedor e model utilizados + return verify_result_judgement(lgpd_judge_file_path) + +def verify_result_judgement(lgpd_judge_file_path) -> bool: + try: + with open(lgpd_judge_file_path, "r", encoding="utf-8") as julgamento: + #print(julgamento.readline()) + + for linha in julgamento: + linha_clean = linha.strip().lower() + + linha_clean = re.sub(r'[*_#>`~]', '', linha_clean).strip() + + linha_clean = linha_clean.strip() + + if(linha_clean == "sim"): + return True + if (linha_clean == "não"): + return False + except Exception as e: + print(f" - ERRO ao ler o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) + +def verifica_se_existe_analise_lgpd(policy_file_path, lgpd_judge_file_path) -> bool: + + provider = os.getenv("AI_PROVIDER").lower() + model = os.getenv("AI_MODEL").lower() + model = re.sub(r'[*_#>`~]', '', model) + + provider_model = provider + model + + if(os.path.exists(policy_file_path) and os.path.exists(lgpd_judge_file_path)): + try: + with open(policy_file_path, "r", encoding="utf-8") as f: + #print(julgamento.readline()) + + for linha in f: + linha_clean = linha.strip().lower() + + linha_clean = re.sub(r'[*_#>`~]', '', linha_clean).strip() + + linha_clean = linha_clean.strip() + if(linha_clean == provider_model): + return True + if(not linha): + return False + except Exception as e: + print(f" - ERRO ao ler o arquivo 'analise_politica_coleta_de_dados.md': {e}", file=sys.stderr) + else: + return False \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index ec911d0..1f7be2f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,214 +1,6 @@ -<<<<<<< HEAD -aiohappyeyeballs==2.6.1 -aiohttp==3.13.0 -aiosignal==1.4.0 -annotated-types==0.7.0 -anthropic==0.72.0 -anyio==4.11.0 -appdirs==1.4.4 -asttokens==3.0.0 -attrs==25.4.0 -backoff==2.2.1 -bcrypt==5.0.0 -beautifulsoup4==4.14.2 -blinker==1.9.0 -browserbase==1.4.0 -build==1.3.0 -cachetools==6.2.0 -certifi==2025.10.5 -cffi==2.0.0 -charset-normalizer==3.4.3 -chromadb==1.1.1 -click==8.3.0 -cohere==5.20.0 -colorama==0.4.6 -coloredlogs==15.0.1 -crewai==0.201.1 -crewai-tools==0.76.0 -cryptography==46.0.2 -dataclasses-json==0.6.7 -decorator==5.2.1 -defusedxml==0.7.1 -deprecation==2.1.0 -diskcache==5.6.3 -distro==1.9.0 -docker==7.1.0 -docstring_parser==0.17.0 -dotenv==0.9.9 -durationpy==0.10 -et_xmlfile==2.0.0 -executing==2.2.1 -fastavro==1.12.1 -filelock==3.19.1 -filetype==1.2.0 -flatbuffers==25.9.23 -frozenlist==1.8.0 -fsspec==2025.9.0 -google-ai-generativelanguage==0.6.15 -google-api-core==2.25.2 -google-api-python-client==2.184.0 -google-auth==2.41.1 -google-auth-httplib2==0.2.0 -google-genai==1.48.0 -google-generativeai==0.8.5 -googleapis-common-protos==1.70.0 -greenlet==3.2.4 -groq==0.33.0 -grpcio==1.75.1 -grpcio-status==1.71.2 -h11==0.16.0 -h2==4.3.0 -hpack==4.1.0 -httpcore==1.0.9 -httplib2==0.31.0 -httptools==0.6.4 -httpx==0.28.1 -httpx-sse==0.4.0 -huggingface-hub==0.35.3 -humanfriendly==10.0 -hyperframe==6.1.0 -idna==3.10 -importlib_metadata==8.7.0 -importlib_resources==6.5.2 -instructor==1.11.3 -ipython==9.6.0 -ipython_pygments_lexers==1.1.1 -jedi==0.19.2 -Jinja2==3.1.6 -jiter==0.10.0 -json5==0.12.1 -json_repair==0.25.2 -jsonpatch==1.33 -jsonpickle==4.1.1 -jsonpointer==3.0.0 -jsonref==1.1.0 -jsonschema==4.25.1 -jsonschema-specifications==2025.9.1 -kubernetes==34.1.0 -lance-namespace==0.0.20 -lance-namespace-urllib3-client==0.0.20 -lancedb==0.25.2 -langchain-classic==1.0.0 -langchain-cohere==0.5.0 -langchain-community==0.4.1 -langchain-core==1.0.3 -langchain-google-genai==2.0.10 -langchain-groq==1.0.0 -langchain-openai==1.0.2 -langchain-text-splitters==1.0.0 -langsmith==0.4.33 -litellm==1.74.9 -lxml==6.0.2 -markdown-it-py==4.0.0 -MarkupSafe==3.0.3 -marshmallow==3.26.1 -matplotlib-inline==0.1.7 -mdurl==0.1.2 -mmh3==5.2.0 -mpmath==1.3.0 -multidict==6.7.0 -mypy_extensions==1.1.0 -nest-asyncio==1.6.0 -networkx==3.5 -numpy==2.3.3 -oauthlib==3.3.1 -onnxruntime==1.23.0 -openai==1.109.1 -openpyxl==3.1.5 -opentelemetry-api==1.37.0 -opentelemetry-exporter-otlp-proto-common==1.37.0 -opentelemetry-exporter-otlp-proto-grpc==1.37.0 -opentelemetry-exporter-otlp-proto-http==1.37.0 -opentelemetry-proto==1.37.0 -opentelemetry-sdk==1.37.0 -opentelemetry-semantic-conventions==0.58b0 -orjson==3.11.3 -overrides==7.7.0 -packaging==25.0 -parso==0.8.5 -pdfminer.six==20250506 -pdfplumber==0.11.7 -pillow==11.3.0 -playwright==1.55.0 -portalocker==2.7.0 -posthog==5.4.0 -prompt_toolkit==3.0.52 -propcache==0.4.0 -proto-plus==1.26.1 -protobuf==5.29.5 -pure_eval==0.2.3 -pyarrow==22.0.0 -pyasn1==0.6.1 -pyasn1_modules==0.4.2 -pybase64==1.4.2 -pycparser==2.23 -pydantic==2.12.0 -pydantic-settings==2.11.0 -pydantic_core==2.41.1 -pyee==13.0.0 -Pygments==2.19.2 -PyJWT==2.10.1 -pylance==0.39.0 -pyparsing==3.2.5 -pypdf==6.1.3 -pypdfium2==4.30.0 -PyPika==0.48.9 -pyproject_hooks==1.2.0 -pyreadline3==3.5.4 -python-dateutil==2.9.0.post0 -python-docx==1.2.0 -python-dotenv==1.1.1 -pytube==15.0.0 -pyvis==0.3.2 -pywin32==311 -PyYAML==6.0.3 -qdrant-client==1.15.1 -referencing==0.36.2 -regex==2025.9.18 -requests==2.32.5 -requests-oauthlib==2.0.0 -requests-toolbelt==1.0.0 -rich==14.1.0 -rpds-py==0.27.1 -rsa==4.9.1 -shellingham==1.5.4 -six==1.17.0 -sniffio==1.3.1 -soupsieve==2.8 -SQLAlchemy==2.0.44 -stack-data==0.6.3 -stagehand==0.5.5 -sympy==1.14.0 -tenacity==9.1.2 -tiktoken==0.12.0 -tokenizers==0.22.1 -tomli==2.2.1 -tomli_w==1.2.0 -tqdm==4.67.1 -traitlets==5.14.3 -typer==0.19.2 -types-PyYAML==6.0.12.20250915 -types-requests==2.32.4.20250913 -typing-inspect==0.9.0 -typing-inspection==0.4.2 -typing_extensions==4.15.0 -uritemplate==4.2.0 -urllib3==2.3.0 -uv==0.8.24 -uvicorn==0.37.0 -watchfiles==1.1.0 -wcwidth==0.2.14 -websocket-client==1.8.0 -websockets==15.0.1 -yarl==1.22.0 -youtube-transcript-api==1.2.3 -zipp==3.23.0 -zstandard==0.25.0 -======= crewai==0.201.1 crewai-tools==0.76.0 python-dotenv==1.1.1 PyYAML==6.0.3 litellm==1.74.9 qdrant-client==1.15.1 ->>>>>>> main From d0252771c36283e76c92b9e175d1d82950750f28 Mon Sep 17 00:00:00 2001 From: Gustavo Saraiva Date: Sun, 30 Nov 2025 18:10:51 -0300 Subject: [PATCH 23/36] =?UTF-8?q?corre=C3=A7=C3=A3o=20chamada=20duplicada?= =?UTF-8?q?=20do=20modo=20lgpd=5Fverify,=20e=20n=C3=A3o=20cria=C3=A7=C3=A3?= =?UTF-8?q?o=20do=20arquivo=20de=20review=20do=20dev?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codewise_lib/cw_runner.py | 1 + scripts/codewise_review_win.py | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index d514c64..2d03b1d 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -5,6 +5,7 @@ from .entradagit import gerar_entrada_automatica, obter_mudancas_staged from crewai import Task, Crew from .lgpd import * +from .code_reviewer import CodeReviewerCrew class CodewiseRunner: diff --git a/scripts/codewise_review_win.py b/scripts/codewise_review_win.py index 3f702cb..311a659 100644 --- a/scripts/codewise_review_win.py +++ b/scripts/codewise_review_win.py @@ -190,9 +190,6 @@ def run_pr_logic(target_selecionado, pushed_branch): upstream_existe = verificar_remote_existe('upstream', repo_path) upstream_renomeado = False - # Chamando função que pergunta ao usuário se ele gostaria de continuar (enviar os dados para provedor ou não) - lgpd_check_user_choice(repo_path, current_branch) - try: if target_selecionado == 'origin' and upstream_existe: print("Renomeando 'upstream' temporariamente para evitar bug do gh...", file=sys.stderr) From 5b717e93a61a4ccde1c3f36f2b3fd20bc8a4b92d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Ren=C3=A3?= Date: Sun, 30 Nov 2025 18:17:20 -0300 Subject: [PATCH 24/36] teste --- codewise_lib/code_reviewer.py | 254 ++++++++------------------------- codewise_lib/config/tasks.yaml | 14 +- codewise_lib/crew.py | 12 ++ codewise_lib/cw_runner.py | 43 +++--- 4 files changed, 101 insertions(+), 222 deletions(-) diff --git a/codewise_lib/code_reviewer.py b/codewise_lib/code_reviewer.py index 1a421cc..6e37684 100644 --- a/codewise_lib/code_reviewer.py +++ b/codewise_lib/code_reviewer.py @@ -1,200 +1,66 @@ import os -import sys -import yaml -import tempfile -from datetime import datetime -from dotenv import load_dotenv -from crewai import Agent, Crew, Process, Task -from .select_llm import create_llm -from .tools.git_analysis_tool import GitAnalysisTool, GitBlameAnalysisTool +import subprocess -class CodeReviewerCrew: - """Crew especializada em avaliar mudanças de código e gerar notas.""" - - def __init__(self, repo_path: str, author_email: str = None, commits_limit: int = 5): - load_dotenv() - self.repo_path = repo_path - self.author_email = author_email - self.commits_limit = commits_limit - - provider = os.getenv("AI_PROVIDER").upper() - model = os.getenv("AI_MODEL") - self.llm = create_llm(provider, model) - - # Ferramentas Git - self.git_tool = GitAnalysisTool() - self.git_blame_tool = GitBlameAnalysisTool() - - # Carrega configurações - base_dir = os.path.dirname(os.path.abspath(__file__)) - config_path = os.path.join(base_dir, "config") - agents_path = os.path.join(config_path, "agents.yaml") - tasks_path = os.path.join(config_path, "tasks.yaml") - - with open(agents_path, "r", encoding="utf-8") as f: - self.agents_config = yaml.safe_load(f) - with open(tasks_path, "r", encoding="utf-8") as f: - self.tasks_config = yaml.safe_load(f) - - def analyze_git_changes(self) -> str: - """Coleta dados do Git para análise.""" - print(f"Analisando mudanças no repositório: {self.repo_path}") - - git_data = self.git_tool.run( - repo_path=self.repo_path, - author_email=self.author_email, - commits_limit=self.commits_limit, - branch="HEAD" - ) - - return git_data - - def create_reviewer_agent(self) -> Agent: - """Cria o agente revisor de código.""" - return Agent( - config=self.agents_config['code_reviewer'], - llm=self.llm, - verbose=True - ) - - def create_review_task(self, git_analysis_data: str) -> Task: - """Cria a task de revisão de código.""" - cfg = self.tasks_config['code_review_scoring'] - - # Substitui placeholders - description = cfg['description'].format( - git_analysis_data=git_analysis_data - ) - - # Determina o nome do arquivo de saída - author_identifier = self.author_email.replace('@', '_').replace('.', '_') if self.author_email else "geral" - expected_output = cfg['expected_output'].format( - author_email=author_identifier - ) - - return Task( - description=description, - expected_output=expected_output, - agent=self.create_reviewer_agent() - ) - - def run_review(self, output_dir: str = None) -> str: - """Executa a revisão completa e gera o arquivo de avaliação.""" - print("Iniciando revisão de código...") - - #Coleta dados do Git - git_data = self.analyze_git_changes() - - if "Erro" in git_data: - print(f"{git_data}") - return None - - #Salva dados em arquivo temporário para referência - temp_file = tempfile.NamedTemporaryFile( - mode='w', - delete=False, - suffix='.txt', - encoding='utf-8' - ) - temp_file.write(git_data) - temp_file.close() - - print(f"Dados Git salvos em: {temp_file.name}") - - #Cria crew de revisão - reviewer = self.create_reviewer_agent() - review_task = self.create_review_task(git_data) - - crew = Crew( - agents=[reviewer], - tasks=[review_task], - process=Process.sequential, - verbose=True - ) - - # Executa a revisão - print("Executando análise de código...") - result = crew.kickoff() - - # Salva resultado - if output_dir is None: - output_dir = os.path.join(self.repo_path, 'code_reviews') - - os.makedirs(output_dir, exist_ok=True) - - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - author_identifier = self.author_email.replace('@', '_').replace('.', '_') if self.author_email else "geral" - output_file = os.path.join( - output_dir, - f"avaliacao_codigo_{author_identifier}_{timestamp}.md" - ) - - with open(output_file, 'w', encoding='utf-8') as f: - f.write(f"# Avaliação de Código\n\n") - f.write(f"**Data da Análise:** {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}\n\n") - if self.author_email: - f.write(f"**Desenvolvedor:** {self.author_email}\n\n") - f.write("---\n\n") - f.write(str(result)) - - print(f"Avaliação concluída e salva em: {output_file}") - - # Limpa arquivo temporário +def coletar_dados_git(repo_path: str, commits_limit: int = 3) -> str: + try: + # Obtém email do usuário try: - os.unlink(temp_file.name) + result = subprocess.run( + ['git', '-C', repo_path, 'config', 'user.email'], + capture_output=True, + text=True, + check=True + ) + user_email = result.stdout.strip() except: - pass + user_email = "Desenvolvedor" + + # Coleta informações dos últimos commits + git_log_cmd = [ + 'git', '-C', repo_path, 'log', + f'-{commits_limit}', + '--pretty=format:%H|%an|%ae|%ad|%s', + '--date=iso', + '--numstat', + 'HEAD' + ] + + log_output = subprocess.check_output( + git_log_cmd, + stderr=subprocess.STDOUT, + text=True + ) - return output_file - - -def main(): - """Função principal para uso via CLI.""" - import argparse - - parser = argparse.ArgumentParser( - description="Code Reviewer - Avalia mudanças de código e gera notas" - ) - parser.add_argument( - "--repo", - type=str, - required=True, - help="Caminho para o repositório Git" - ) - parser.add_argument( - "--author", - type=str, - help="Email do autor para filtrar commits (opcional)" - ) - parser.add_argument( - "--commits", - type=int, - default=5, - help="Número de commits a analisar (padrão: 5)" - ) - parser.add_argument( - "--output", - type=str, - help="Diretório de saída para o arquivo de avaliação (opcional)" - ) - - args = parser.parse_args() - - reviewer = CodeReviewerCrew( - repo_path=args.repo, - author_email=args.author, - commits_limit=args.commits - ) - - output_file = reviewer.run_review(output_dir=args.output) - - if output_file: - print(f"\nRevisão completa! Arquivo gerado: {output_file}") - else: - print("\nFalha na revisão de código.") - sys.exit(1) - - -if __name__ == "__main__": - main() + # Monta o contexto para análise + resultado = [] + resultado.append("=" * 80) + resultado.append(f"ANÁLISE DE CÓDIGO - {user_email}") + resultado.append("=" * 80) + resultado.append("") + resultado.append(f"Últimos {commits_limit} commits:") + resultado.append("") + resultado.append(log_output) + resultado.append("") + + # Pega diff dos últimos commits + for i in range(commits_limit): + try: + diff_cmd = ['git', '-C', repo_path, 'show', f'HEAD~{i}', '--unified=3'] + diff_output = subprocess.check_output( + diff_cmd, + stderr=subprocess.STDOUT, + text=True + ) + resultado.append(f"--- Mudanças do commit HEAD~{i} ---") + resultado.append(diff_output[:3000]) # Limita tamanho + resultado.append("") + except: + pass + + return "\n".join(resultado) + + except subprocess.CalledProcessError as e: + return f"Erro ao coletar dados Git: {e.output}" + except Exception as e: + return f"Erro: {str(e)}" diff --git a/codewise_lib/config/tasks.yaml b/codewise_lib/config/tasks.yaml index 5678d33..1eef672 100644 --- a/codewise_lib/config/tasks.yaml +++ b/codewise_lib/config/tasks.yaml @@ -66,7 +66,7 @@ lgpd_judging: code_review_scoring: description: > - Analise as mudanças de código do desenvolvedor com base nos dados fornecidos: "{git_analysis_data}". + Com base nas mudanças de código fornecidas: "{input}", Avalie criteriosamente os seguintes aspectos: 1. Qualidade do Código (0-2.5 pontos): Legibilidade, clareza, nomenclatura, organização @@ -81,13 +81,15 @@ code_review_scoring: - Code smells e anti-patterns - Eficiência e otimização - Seja rigoroso mas justo. Uma nota 10 deve ser excepcional, não comum. - Notas entre 7-8.5 são boas, 8.5-9.5 são muito boas, 9.5-10 são excepcionais. - Abaixo de 7 indica necessidade de melhorias significativas. + Seja rigoroso mas justo. Uma nota 10.0 deve ser excepcional, não comum. + Notas entre 7.0-8.5 são boas, 8.5-9.5 são muito boas, 9.5-10.0 são excepcionais. + Abaixo de 7.0 indica necessidade de melhorias significativas. + + A resposta deve ser **obrigatoriamente em Português do Brasil**. expected_output: > - Documento avaliacao_codigo_{author_email}.md contendo: + Relatório de avaliação em markdown contendo: - Nome do desenvolvedor e email - - Nota final (0-10) + - Nota final (0-10.0) - Breakdown de pontos por categoria - Justificativa detalhada e técnica para a nota - Pontos fortes identificados diff --git a/codewise_lib/crew.py b/codewise_lib/crew.py index 4d0162c..f9a4204 100644 --- a/codewise_lib/crew.py +++ b/codewise_lib/crew.py @@ -107,6 +107,11 @@ def task_policy(self) -> Task: def task_judging(self) -> Task: cfg = self.tasks_config['lgpd_judging'] return Task(description=cfg['description'], expected_output=cfg['expected_output'], agent=self.lgpd_judge()) + + @task + def task_code_review(self) -> Task: + cfg = self.tasks_config['code_review_scoring'] + return Task(description=cfg['description'], expected_output=cfg['expected_output'], agent=self.code_reviewer()) @crew @@ -129,4 +134,11 @@ def lgpd_crew(self) -> Crew: agents=[self.dataCollect_policy_analytics(), self.lgpd_judge()], tasks=[self.task_policy(), self.task_judging()], process=Process.sequential + ) + + def code_review_crew(self) -> Crew: + return Crew( + agents=[self.code_reviewer()], + tasks=[self.task_code_review()], + process=Process.sequential ) \ No newline at end of file diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index d514c64..c5eeec9 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -5,6 +5,7 @@ from .entradagit import gerar_entrada_automatica, obter_mudancas_staged from crewai import Task, Crew from .lgpd import * +from .code_reviewer import coletar_dados_git class CodewiseRunner: @@ -119,31 +120,29 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): except Exception as e: print(f" - ERRO ao salvar o arquivo 'sugestoes_aprendizado.md': {e}", file=sys.stderr) - # Executa Code Review automaticamente após a análise + # Executa Code Review automaticamente após a análise (similar ao LGPD) print("\n🔍 Gerando avaliação de código...", file=sys.stderr) try: - import subprocess - try: - result = subprocess.run( - ['git', '-C', caminho_repo, 'config', 'user.email'], - capture_output=True, - text=True, - check=True - ) - user_email = result.stdout.strip() - except: - user_email = None + # Coleta dados Git + dados_git = coletar_dados_git(caminho_repo, commits_limit=3) - reviewer = CodeReviewerCrew( - repo_path=caminho_repo, - author_email=user_email, - commits_limit=3 - ) - - output_file = reviewer.run_review(output_dir=output_dir_path) - - if output_file: - print(f" - Arquivo de avaliação salvo: {os.path.basename(output_file)}", file=sys.stderr) + if "Erro" not in dados_git: + # Cria crew de code review + code_review_crew = codewise_instance.code_review_crew() + + # Executa a avaliação + code_review_crew.kickoff(inputs={'input': dados_git}) + + # Salva o resultado + resultado_review = code_review_crew.tasks[0].output + review_file_path = os.path.join(output_dir_path, "avaliacao_codigo.md") + + with open(review_file_path, "w", encoding="utf-8") as f: + f.write(str(resultado_review)) + + print(f" - Arquivo 'avaliacao_codigo.md' salvo com sucesso.", file=sys.stderr) + else: + print(f" - Aviso: {dados_git}", file=sys.stderr) except Exception as e: print(f" - Aviso: Não foi possível gerar avaliação de código: {str(e)}", file=sys.stderr) From b70be9784a700edec38123a9d898951fe6c3d85d Mon Sep 17 00:00:00 2001 From: Matheus Magnago Date: Sun, 30 Nov 2025 18:56:34 -0300 Subject: [PATCH 25/36] WORKING!!!!! --- codewise_lib/cw_runner.py | 4 ---- scripts/codewise_review_win.py | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index 9f80f3e..c5eeec9 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -5,11 +5,7 @@ from .entradagit import gerar_entrada_automatica, obter_mudancas_staged from crewai import Task, Crew from .lgpd import * -<<<<<<< HEAD -from .code_reviewer import CodeReviewerCrew -======= from .code_reviewer import coletar_dados_git ->>>>>>> branchtemp class CodewiseRunner: diff --git a/scripts/codewise_review_win.py b/scripts/codewise_review_win.py index 311a659..7f715e1 100644 --- a/scripts/codewise_review_win.py +++ b/scripts/codewise_review_win.py @@ -37,8 +37,8 @@ def run_codewise_mode(mode, repo_path, branch_name): stdin=subprocess.DEVNULL ) - #if result.stderr: - # print(result.stderr, file=sys.stderr) + if result.stderr: + print(result.stderr, file=sys.stderr) return result.stdout.strip() except subprocess.CalledProcessError as e: From 224fced8215cff4500abb9aab5a373d7de7cfa22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Ren=C3=A3?= Date: Sun, 30 Nov 2025 22:26:50 -0300 Subject: [PATCH 26/36] =?UTF-8?q?adicionando=20sistema=20de=20notifica?= =?UTF-8?q?=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codewise_lib/config/tasks.yaml | 2 +- codewise_lib/cw_runner.py | 15 ++++ codewise_lib/notificacao_gestor.py | 128 +++++++++++++++++++++++++++++ requirements.txt | 1 + 4 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 codewise_lib/notificacao_gestor.py diff --git a/codewise_lib/config/tasks.yaml b/codewise_lib/config/tasks.yaml index 1eef672..6572754 100644 --- a/codewise_lib/config/tasks.yaml +++ b/codewise_lib/config/tasks.yaml @@ -73,7 +73,7 @@ code_review_scoring: 2. Arquitetura e Design (0-2.5 pontos): Aderência a SOLID, padrões de projeto, separação de responsabilidades 3. Boas Práticas (0-2.5 pontos): Tratamento de erros, segurança, performance, testes 4. Impacto e Complexidade (0-2.5 pontos): Tamanho da mudança, complexidade ciclomática, risco de bugs - + Considere também: - Consistência com o código existente - Documentação e comentários quando necessários diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index c5eeec9..54c23e5 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -6,6 +6,7 @@ from crewai import Task, Crew from .lgpd import * from .code_reviewer import coletar_dados_git +from .notificacao_gestor import processar_avaliacao_e_notificar class CodewiseRunner: @@ -141,6 +142,20 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): f.write(str(resultado_review)) print(f" - Arquivo 'avaliacao_codigo.md' salvo com sucesso.", file=sys.stderr) + + # Envia para Firebase e notifica gestor via Telegram + try: + import subprocess + email_dev = subprocess.check_output( + ['git', '-C', caminho_repo, 'config', 'user.email'], + text=True + ).strip() + except: + email_dev = "desconhecido" + + print("\n📤 Enviando avaliação para o gestor...", file=sys.stderr) + processar_avaliacao_e_notificar(review_file_path, email_dev, caminho_repo) + else: print(f" - Aviso: {dados_git}", file=sys.stderr) diff --git a/codewise_lib/notificacao_gestor.py b/codewise_lib/notificacao_gestor.py new file mode 100644 index 0000000..6a1f72e --- /dev/null +++ b/codewise_lib/notificacao_gestor.py @@ -0,0 +1,128 @@ +import os +import sys +import re +import requests +from datetime import datetime +from dotenv import load_dotenv + +load_dotenv() + + +def enviar_telegram(mensagem: str) -> bool: + telegram_token = os.getenv("TELEGRAM_BOT_TOKEN") + telegram_chat_id = os.getenv("TELEGRAM_CHAT_ID") + + if not telegram_token or not telegram_chat_id: + print("⚠️ TELEGRAM_BOT_TOKEN ou TELEGRAM_CHAT_ID não configurados no .env", file=sys.stderr) + return False + + try: + url = f"https://api.telegram.org/bot{telegram_token}/sendMessage" + + payload = { + "chat_id": telegram_chat_id, + "text": mensagem, + "parse_mode": "Markdown" + } + + response = requests.post(url, json=payload, timeout=10) + + if response.status_code == 200: + print("✅ Notificação enviada via Telegram", file=sys.stderr) + return True + else: + print(f"⚠️ Erro ao enviar Telegram: {response.status_code}", file=sys.stderr) + return False + + except Exception as e: + print(f"⚠️ Erro ao conectar com Telegram: {str(e)}", file=sys.stderr) + return False + + +def processar_avaliacao_e_notificar(caminho_arquivo: str, email_dev: str, repo_path: str) -> bool: + try: + # Lê o arquivo linha por linha (padrão lgpd.py) + nota = 0.0 + justificativa_linhas = [] + capturando_breakdown = False + + with open(caminho_arquivo, 'r', encoding='utf-8') as f: + for linha in f: + # Limpa a linha (padrão lgpd.py) + linha_clean = linha.strip() + + # Procura pela nota + if 'nota final' in linha_clean.lower(): + # Remove caracteres especiais (padrão lgpd.py) + linha_limpa = re.sub(r'[*_#>`~]', '', linha_clean) + linha_limpa = linha_limpa.strip() + + # Extrai o número + nota_match = re.search(r'(\d+\.?\d*)', linha_limpa) + if nota_match: + nota = float(nota_match.group(1)) + print(f" ✓ Nota encontrada: {nota}/10", file=sys.stderr) + + # Procura pelo início do breakdown + if 'breakdown de pontos' in linha_clean.lower(): + capturando_breakdown = True + continue + + # Para de capturar quando encontrar "Justificativa Detalhada" + if capturando_breakdown and 'justificativa detalhada' in linha_clean.lower(): + break + + # Captura linhas do breakdown até "Justificativa Detalhada" + if capturando_breakdown: + # Remove caracteres especiais (padrão lgpd.py) + linha_limpa = re.sub(r'[*_#>`~]', '', linha_clean) + linha_limpa = linha_limpa.strip() + + if linha_limpa: + # Adiciona a linha com quebra de linha + justificativa_linhas.append(linha_limpa) + + # Monta a justificativa com quebras de linha + justificativa = '\n'.join(justificativa_linhas) + + # Limita a 4000 caracteres para o Telegram + if len(justificativa) > 4000: + justificativa = justificativa[:3997] + "..." + + if justificativa: + print(f" ✓ Justificativa extraída ({len(justificativa)} chars)", file=sys.stderr) + else: + print(f" ⚠️ Justificativa não encontrada", file=sys.stderr) + justificativa = "Avaliação concluída." + + # Obtém nome do repositório + repo_nome = os.path.basename(repo_path) + + # Prepara mensagem para Telegram + # Nota é de 0 a 10, então ajusta os limites + emoji_nota = "🟢" if nota >= 8.5 else "🟡" if nota >= 7.0 else "🔴" + + # Escapa caracteres especiais do Markdown + justificativa_escaped = justificativa.replace('*', '\\*').replace('_', '\\_').replace('[', '\\[').replace('`', '\\`') + + mensagem = f""" +{emoji_nota} *Nova Avaliação de Código* + +👤 *Desenvolvedor:* {email_dev} +📦 *Repositório:* {repo_nome} +📊 *Nota:* {nota}/10 + +📝 *Resumo:* +{justificativa_escaped} + +📅 *Data:* {datetime.now().strftime("%d/%m/%Y %H:%M")} +""" + + # Envia notificação + telegram_ok = enviar_telegram(mensagem) + + return telegram_ok + + except Exception as e: + print(f"⚠️ Erro ao processar avaliação: {str(e)}", file=sys.stderr) + return False diff --git a/requirements.txt b/requirements.txt index 1f7be2f..50a9f72 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ python-dotenv==1.1.1 PyYAML==6.0.3 litellm==1.74.9 qdrant-client==1.15.1 +requests==2.31.0 From ab8bb48d2cdbf6376e44f76b5185297e8eaa8a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Ren=C3=A3?= Date: Sun, 30 Nov 2025 23:29:20 -0300 Subject: [PATCH 27/36] =?UTF-8?q?atualizando=20agentes=20e=20condi=C3=A7?= =?UTF-8?q?=C3=B5es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codewise_lib/config/tasks.yaml | 2 +- codewise_lib/cw_runner.py | 4 +--- codewise_lib/notificacao_gestor.py | 12 ++++++------ requirements.txt | 2 +- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/codewise_lib/config/tasks.yaml b/codewise_lib/config/tasks.yaml index 6572754..9e71fb0 100644 --- a/codewise_lib/config/tasks.yaml +++ b/codewise_lib/config/tasks.yaml @@ -91,7 +91,7 @@ code_review_scoring: - Nome do desenvolvedor e email - Nota final (0-10.0) - Breakdown de pontos por categoria - - Justificativa detalhada e técnica para a nota + - **Justificativa Detalhada** e técnica para a nota - Pontos fortes identificados - Pontos de melhoria com exemplos específicos - Recomendações práticas e acionáveis diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index 54c23e5..e45d569 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -31,7 +31,6 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): return 0 - contexto_para_ia = "" if modo == 'lint': @@ -142,8 +141,7 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): f.write(str(resultado_review)) print(f" - Arquivo 'avaliacao_codigo.md' salvo com sucesso.", file=sys.stderr) - - # Envia para Firebase e notifica gestor via Telegram + # Envia notificação para o gestor try: import subprocess email_dev = subprocess.check_output( diff --git a/codewise_lib/notificacao_gestor.py b/codewise_lib/notificacao_gestor.py index 6a1f72e..64b8f33 100644 --- a/codewise_lib/notificacao_gestor.py +++ b/codewise_lib/notificacao_gestor.py @@ -41,19 +41,19 @@ def enviar_telegram(mensagem: str) -> bool: def processar_avaliacao_e_notificar(caminho_arquivo: str, email_dev: str, repo_path: str) -> bool: try: - # Lê o arquivo linha por linha (padrão lgpd.py) + # Lê o arquivo linha por linha nota = 0.0 justificativa_linhas = [] capturando_breakdown = False with open(caminho_arquivo, 'r', encoding='utf-8') as f: for linha in f: - # Limpa a linha (padrão lgpd.py) + # Limpa a linha linha_clean = linha.strip() # Procura pela nota if 'nota final' in linha_clean.lower(): - # Remove caracteres especiais (padrão lgpd.py) + # Remove caracteres especiais linha_limpa = re.sub(r'[*_#>`~]', '', linha_clean) linha_limpa = linha_limpa.strip() @@ -68,13 +68,13 @@ def processar_avaliacao_e_notificar(caminho_arquivo: str, email_dev: str, repo_p capturando_breakdown = True continue - # Para de capturar quando encontrar "Justificativa Detalhada" + # Para de capturar quando encontrar "Justificativa detalhada" if capturando_breakdown and 'justificativa detalhada' in linha_clean.lower(): break - # Captura linhas do breakdown até "Justificativa Detalhada" + # Captura linhas do breakdown até "Justificativa" if capturando_breakdown: - # Remove caracteres especiais (padrão lgpd.py) + # Remove caracteres especiais linha_limpa = re.sub(r'[*_#>`~]', '', linha_clean) linha_limpa = linha_limpa.strip() diff --git a/requirements.txt b/requirements.txt index 50a9f72..66d22b4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ python-dotenv==1.1.1 PyYAML==6.0.3 litellm==1.74.9 qdrant-client==1.15.1 -requests==2.31.0 +requests==2.32.3 From b9a1c7ade90fa2fa9f6f2bbad586d459408b35e9 Mon Sep 17 00:00:00 2001 From: Matheus Magnago Date: Mon, 1 Dec 2025 08:56:12 -0300 Subject: [PATCH 28/36] Adjusting the evaluation tasks and fixing the grades justification content capturing --- codewise_lib/config/tasks.yaml | 3 +++ codewise_lib/notificacao_gestor.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/codewise_lib/config/tasks.yaml b/codewise_lib/config/tasks.yaml index 9e71fb0..be5983e 100644 --- a/codewise_lib/config/tasks.yaml +++ b/codewise_lib/config/tasks.yaml @@ -86,6 +86,9 @@ code_review_scoring: Abaixo de 7.0 indica necessidade de melhorias significativas. A resposta deve ser **obrigatoriamente em Português do Brasil**. + + No tópico Justificativa Detalhada, informe o fim do mesmo em uma linha única após todo seu conteúdo (Fim Justificativa Detalhada). + expected_output: > Relatório de avaliação em markdown contendo: - Nome do desenvolvedor e email diff --git a/codewise_lib/notificacao_gestor.py b/codewise_lib/notificacao_gestor.py index 64b8f33..0695801 100644 --- a/codewise_lib/notificacao_gestor.py +++ b/codewise_lib/notificacao_gestor.py @@ -69,7 +69,7 @@ def processar_avaliacao_e_notificar(caminho_arquivo: str, email_dev: str, repo_p continue # Para de capturar quando encontrar "Justificativa detalhada" - if capturando_breakdown and 'justificativa detalhada' in linha_clean.lower(): + if capturando_breakdown and 'fim justificativa' in linha_clean.lower(): break # Captura linhas do breakdown até "Justificativa" From 574db9270e5d037b43279b233535d770d1832fb7 Mon Sep 17 00:00:00 2001 From: Gustavo Saraiva Date: Tue, 2 Dec 2025 10:54:27 -0300 Subject: [PATCH 29/36] =?UTF-8?q?docs:=20adi=C3=A7=C3=A3o=20de=20docstring?= =?UTF-8?q?s=20nas=20fun=C3=A7=C3=B5es=20do=20projeto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codewise_lib/code_reviewer.py | 17 ++++-- codewise_lib/crew.py | 33 ++++++++++- codewise_lib/cw_runner.py | 43 ++++++++++++--- codewise_lib/entradagit.py | 61 +++++++++++++++------ codewise_lib/lgpd.py | 44 +++++++++++---- codewise_lib/notificacao_gestor.py | 44 +++++++++------ codewise_lib/select_llm.py | 13 +++++ scripts/codewise_review_win.py | 88 +++++++++++++++++++++++++++--- scripts/help.py | 3 + scripts/install_hook.py | 24 ++++++++ 10 files changed, 302 insertions(+), 68 deletions(-) diff --git a/codewise_lib/code_reviewer.py b/codewise_lib/code_reviewer.py index 6e37684..0a16bb8 100644 --- a/codewise_lib/code_reviewer.py +++ b/codewise_lib/code_reviewer.py @@ -3,8 +3,17 @@ def coletar_dados_git(repo_path: str, commits_limit: int = 3) -> str: + """ + Coleta informações detalhadas do repositório Git para análise de código. + + Args: + repo_path: Caminho para o repositório Git local + commits_limit: Número máximo de commits a analisar (padrão: 3) + + Returns: + str: Texto formatado com informações dos commits e diffs para análise + """ try: - # Obtém email do usuário try: result = subprocess.run( ['git', '-C', repo_path, 'config', 'user.email'], @@ -16,7 +25,7 @@ def coletar_dados_git(repo_path: str, commits_limit: int = 3) -> str: except: user_email = "Desenvolvedor" - # Coleta informações dos últimos commits + #coleta de logs dos commits anteriores git_log_cmd = [ 'git', '-C', repo_path, 'log', f'-{commits_limit}', @@ -32,7 +41,7 @@ def coletar_dados_git(repo_path: str, commits_limit: int = 3) -> str: text=True ) - # Monta o contexto para análise + #formatação do resultado final resultado = [] resultado.append("=" * 80) resultado.append(f"ANÁLISE DE CÓDIGO - {user_email}") @@ -43,7 +52,7 @@ def coletar_dados_git(repo_path: str, commits_limit: int = 3) -> str: resultado.append(log_output) resultado.append("") - # Pega diff dos últimos commits + #coleta de diffs dos commits anteriores for i in range(commits_limit): try: diff_cmd = ['git', '-C', repo_path, 'show', f'HEAD~{i}', '--unified=3'] diff --git a/codewise_lib/crew.py b/codewise_lib/crew.py index f9a4204..d87a19c 100644 --- a/codewise_lib/crew.py +++ b/codewise_lib/crew.py @@ -17,19 +17,25 @@ class Codewise: """Classe principal da crew Codewise""" def __init__(self, commit_message: str = ""): + """ + Inicializa a crew Codewise com agentes e configurações. + + Args: + commit_message: Mensagem de commit ou contexto para análise (opcional) + """ load_dotenv() + #configurações iniciais da llm e agentes self.commit_message = commit_message - # self para utilizar na task de analise da politica self.provider = os.getenv("AI_PROVIDER").upper() self.model = os.getenv("AI_MODEL") - ## self.llm = create_llm(self.provider,self.model) - #tools + #tools iniciais self.web_search_tool = WebsiteSearchTool() self.git_analysis_tool = GitAnalysisTool() self.git_blame_tool = GitBlameAnalysisTool() + #carregamento de configurações de agentes e tarefas base_dir = os.path.dirname(os.path.abspath(__file__)) config_path = os.path.join(base_dir, "config") agents_path = os.path.join(config_path, "agents.yaml") @@ -44,6 +50,7 @@ def __init__(self, commit_message: str = ""): print(f"Erro: Arquivo de configuração não encontrado: {e}") sys.exit(1) + #definição dos agentes disponíveis @agent def senior_architect(self) -> Agent: return Agent(config=self.agents_config['senior_architect'], llm=self.llm, verbose=False) @agent @@ -66,6 +73,7 @@ def lgpd_judge(self) -> Agent: return Agent(config=self.agents_config['lgpd_judg @agent def code_reviewer(self) -> Agent: return Agent(config=self.agents_config['code_reviewer'], llm=self.llm, verbose=True) + #definição das tarefas disponíveis para cada agente @task def task_estrutura(self) -> Task: cfg = self.tasks_config['analise_estrutura'] @@ -114,6 +122,7 @@ def task_code_review(self) -> Task: return Task(description=cfg['description'], expected_output=cfg['expected_output'], agent=self.code_reviewer()) + #definição da crew principal @crew def crew(self) -> Crew: return Crew( @@ -123,6 +132,12 @@ def crew(self) -> Crew: ) def summary_crew(self) -> Crew: + """ + Cria uma crew especializada em gerar resumos executivos. + + Returns: + Crew: Instância da crew de resumo + """ return Crew( agents=[self.summary_specialist()], tasks=[self.task_summarize()], @@ -130,6 +145,12 @@ def summary_crew(self) -> Crew: ) def lgpd_crew(self) -> Crew: + """ + Cria uma crew especializada em análise de conformidade com a LGPD. + + Returns: + Crew: Instância da crew de análise LGPD + """ return Crew( agents=[self.dataCollect_policy_analytics(), self.lgpd_judge()], tasks=[self.task_policy(), self.task_judging()], @@ -137,6 +158,12 @@ def lgpd_crew(self) -> Crew: ) def code_review_crew(self) -> Crew: + """ + Cria uma crew especializada em revisão e avaliação de código. + + Returns: + Crew: Instância da crew de code review + """ return Crew( agents=[self.code_reviewer()], tasks=[self.task_code_review()], diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index e45d569..a44e919 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -10,16 +10,32 @@ class CodewiseRunner: + """ + Classe responsável por organizar a execução das análises do CodeWise. + Gerencia diferentes modos de operação (lint, titulo, descricao, analise, lgpd_verify). + """ def __init__(self): + """ + Inicializa o CodewiseRunner com os caminhos necessários. + """ self.BASE_DIR = os.path.dirname(os.path.abspath(__file__)) self.caminho_entrada = os.path.join(self.BASE_DIR, ".entrada_temp.txt") def executar(self, caminho_repo: str, nome_branch: str, modo: str): + """ + Executa a análise de código no modo especificado. + + Args: + caminho_repo: Caminho para o repositório Git + nome_branch: Nome da branch a ser analisada + modo: Modo de operação ('lint', 'titulo', 'descricao', 'analise', 'lgpd_verify') + """ caminho_dir_lgpd = os.path.join(caminho_repo, "analises-julgamento-lgpd") policy_file_path = os.path.join(caminho_dir_lgpd, "analise_politica_coleta_de_dados.md") lgpd_judge_file_path = os.path.join(caminho_dir_lgpd, "julgamento_lgpd.md") + if(modo == 'lgpd_verify'): if(verifica_se_existe_analise_lgpd(policy_file_path, lgpd_judge_file_path)): print("\nA análise e verificação LGPD já foi feita anteriormente para este mesmo provedor e modelo api key. Por conta disso, não será necessário efetuá-la novamente! \n",file=sys.stderr) @@ -32,7 +48,6 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): contexto_para_ia = "" - if modo == 'lint': resultado_git = obter_mudancas_staged(caminho_repo) @@ -53,16 +68,19 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): codewise_instance = Codewise(commit_message=contexto_para_ia) resultado_final = "" + if modo == 'titulo': agent = codewise_instance.summary_specialist() task = Task(description=f"Crie um título de PR conciso no padrão Conventional Commits para as seguintes mudanças. A resposta deve ser APENAS o título, **obrigatoriamente em Português do Brasil**, sem aspas, acentos graves ou qualquer outro texto:\n{contexto_para_ia}", expected_output="Um único título de PR.", agent=agent) resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() + elif modo == 'descricao': agent = codewise_instance.summary_specialist() task = Task(description=f"Crie uma descrição de um parágrafo **obrigatoriamente em Português do Brasil** para um Pull Request para as seguintes mudanças:\n{contexto_para_ia}", expected_output="Um único parágrafo de texto.", agent=agent) resultado_final = Crew(agents=[agent], tasks=[task]).kickoff() + elif modo == 'analise': analysis_crew = codewise_instance.crew() analysis_crew.kickoff(inputs={'input': contexto_para_ia}) @@ -120,20 +138,18 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): except Exception as e: print(f" - ERRO ao salvar o arquivo 'sugestoes_aprendizado.md': {e}", file=sys.stderr) - # Executa Code Review automaticamente após a análise (similar ao LGPD) + + #avaliação de código e notificação para o gestor print("\n🔍 Gerando avaliação de código...", file=sys.stderr) + #coleta de dados git para análise try: - # Coleta dados Git dados_git = coletar_dados_git(caminho_repo, commits_limit=3) if "Erro" not in dados_git: - # Cria crew de code review code_review_crew = codewise_instance.code_review_crew() - # Executa a avaliação code_review_crew.kickoff(inputs={'input': dados_git}) - # Salva o resultado resultado_review = code_review_crew.tasks[0].output review_file_path = os.path.join(output_dir_path, "avaliacao_codigo.md") @@ -141,7 +157,8 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): f.write(str(resultado_review)) print(f" - Arquivo 'avaliacao_codigo.md' salvo com sucesso.", file=sys.stderr) - # Envia notificação para o gestor + + #obtenção do email do desenvolvedor try: import subprocess email_dev = subprocess.check_output( @@ -159,7 +176,8 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): except Exception as e: print(f" - Aviso: Não foi possível gerar avaliação de código: {str(e)}", file=sys.stderr) - + + elif modo == 'lint': agent = codewise_instance.quality_consultant() task = Task(description=f"Analise rapidamente as seguintes mudanças de código ('git diff') e aponte APENAS problemas óbvios ou code smells. A resposta deve ser **obrigatoriamente em Português do Brasil**. Seja conciso. Se não houver problemas, retorne 'Nenhum problema aparente detectado.'.\n\nCódigo a ser analisado:\n{contexto_para_ia}", expected_output="Uma lista curta em bullet points com sugestões, ou uma mensagem de que está tudo ok.", agent=agent) @@ -172,6 +190,15 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): def _ler_arquivo(self, file_path: str) -> str: + """ + Lê o conteúdo de um arquivo de texto. + + Args: + file_path: Caminho do arquivo a ser lido + + Returns: + str: Conteúdo do arquivo ou string vazia se não encontrado + """ try: with open(file_path, "r", encoding="utf-8") as f: return f.read() except FileNotFoundError: return "" diff --git a/codewise_lib/entradagit.py b/codewise_lib/entradagit.py index d2a7bd2..024efbf 100644 --- a/codewise_lib/entradagit.py +++ b/codewise_lib/entradagit.py @@ -3,42 +3,61 @@ import sys def run_git_command(command, repo_path): - """Função auxiliar para executar comandos Git e capturar a saída.""" + """ + Função auxiliar para executar comandos Git e capturar a saída. + + Args: + command: Lista com o comando Git e seus argumentos + repo_path: Caminho do repositório onde executar o comando + + Returns: + str ou None: Saída do comando se tiver ok, string vazia para erros não fatais, None para erros fatais + """ try: - # Usamos repo_path como o diretório de trabalho para o comando + #se não houver erro, retorna a saída do comando normal result = subprocess.check_output(command, cwd=repo_path, text=True, encoding='utf-8', stderr=subprocess.PIPE) return result.strip() except subprocess.CalledProcessError as e: - # Se o comando falhar, mas o erro não for fatal, imprime no stderr e continua + #se for um erro não fatal, como branch inexistente, retorna string vazia para tratar depois e não quebrar o fluxo if e.stderr: print(f"Aviso do Git: {e.stderr.strip()}", file=sys.stderr) - return "" # Retorna vazio para erros não fatais (ex: branch não encontrada no remote) + return "" except FileNotFoundError: + #erros fatais, como o git não estar instalado ou coisas do tipo print("ERRO: O executável 'git' não foi encontrado. Verifique se o Git está instalado e no PATH.", file=sys.stderr) - return None # Retorna None para erros fatais + return None def gerar_entrada_automatica(caminho_repo, caminho_saida, nome_branch): + """ + Gera automaticamente o arquivo de entrada com commits e diffs para análise. + + Args: + caminho_repo: Caminho para o repositório Git + caminho_saida: Caminho onde salvar o arquivo de entrada gerado + nome_branch: Nome da branch a ser analisada + + Returns: + bool: True se gerado com sucesso, False caso contrário + """ try: - # 1. Busca atualizações do repositório remoto + #busca alguma alteração que tiver na branch remota print("🔄 Buscando atualizações do repositório remoto...", file=sys.stderr) run_git_command(["git", "fetch", "origin", "--prune"], caminho_repo) - # 2. Define a branch base para comparação + #define a branch remota a ser comparada já verificando se existe branch_remota_str = f'origin/{nome_branch}' - # Verifica se a branch existe no remote remote_branch_exists = run_git_command(["git", "show-ref", "--verify", f"refs/remotes/{branch_remota_str}"], caminho_repo) - default_branch_name = "main" # Assume 'main' como padrão + default_branch_name = "main" base_ref_str = f'origin/{default_branch_name}' if remote_branch_exists: - # Se a branch já existe, a base é ela mesma no remote base_ref_str = branch_remota_str print(f"✅ Branch '{nome_branch}' já existe no remote. Analisando novos commits desde o último push.", file=sys.stderr) else: print(f"✅ Branch '{nome_branch}' é nova. Comparando com a branch principal remota ('{default_branch_name}').", file=sys.stderr) - # 3. Pega a lista de mensagens de commit + #pega a lista de commits range_commits = f"{base_ref_str}..{nome_branch}" log_commits = run_git_command(["git", "log", "--pretty=format:- %s", range_commits], caminho_repo) @@ -48,10 +67,10 @@ def gerar_entrada_automatica(caminho_repo, caminho_saida, nome_branch): commits_pendentes = log_commits.splitlines() - # 4. Gera o diff incremental + #pega o diff completo dos commits diff_completo = run_git_command(["git", "diff", f"{base_ref_str}..{nome_branch}"], caminho_repo) - # 5. Monta o texto final + #monta o texto final para o arquivo de entrada entrada = [f"Analisando {len(commits_pendentes)} novo(s) commit(s).\n\nMensagens de commit:\n"] entrada.extend(commits_pendentes) entrada.append(f"\n{'='*80}\nDiferenças de código consolidadas a serem analisadas:\n{diff_completo}") @@ -64,19 +83,27 @@ def gerar_entrada_automatica(caminho_repo, caminho_saida, nome_branch): return False def obter_mudancas_staged(repo_path="."): - """Verifica o estado do repositório para o modo lint usando subprocess.""" + """ + Verifica o estado do repositório para o modo lint. + + Args: + repo_path: Caminho para o repositório Git (padrão: diretório atual) + + Returns: + str ou None: Diff das mudanças staged, mensagem de aviso, ou None se não houver mudanças + """ try: - # 1. Verifica a 'staging area' + #verifica a área de stage diff_staged = run_git_command(["git", "diff", "--cached"], repo_path) if diff_staged: return diff_staged - # 2. Se o stage está limpo, verifica o 'working directory' + #se não tiver nada na staging area, verifica se tem mudanças no working dir diff_working_dir = run_git_command(["git", "diff"], repo_path) if diff_working_dir: return "AVISO: Nenhuma mudança na 'staging area', mas existem modificações não adicionadas.\nUse 'git add ' para prepará-las para a análise." - # 3. Se ambos estiverem limpos, retorna None + #se ambos estiverem limpos, retorna None return None except Exception as e: print(f"Erro em 'entradagit.py' ao obter staged changes: {e}", file=sys.stderr) diff --git a/codewise_lib/lgpd.py b/codewise_lib/lgpd.py index ed1beab..008a696 100644 --- a/codewise_lib/lgpd.py +++ b/codewise_lib/lgpd.py @@ -5,6 +5,18 @@ from .crew import Codewise def verify_lgpd(caminho_repo: str, caminho_dir_lgpd: str, policy_file_path: str, lgpd_judge_file_path: str) -> bool: + """ + Executa a verificação de conformidade LGPD do provedor de IA definido no .env. + + Args: + caminho_repo: Caminho para o repositório Git + caminho_dir_lgpd: Diretório para salvar as análises LGPD + policy_file_path: Caminho do arquivo de análise de política + lgpd_judge_file_path: Caminho do arquivo de julgamento LGPD + + Returns: + bool: True se aprovado, False caso contrário + """ # Tentativa de colocar a analise lgpd para rodar antes do envio dos dados sensiveis # instancia sem passar o commit como contexto codewise_instance = Codewise() @@ -23,9 +35,6 @@ def verify_lgpd(caminho_repo: str, caminho_dir_lgpd: str, policy_file_path: str, # salvando o resultado da analise da politica de coleta de dados resultado_policy = lgpd_check_crew.tasks[0].output - # definindo caminho e nome do arquivo final. - #policy_file_path = os.path.join(output_dir_path, "analise_politica_coleta_de_dados.md") - try: with open(policy_file_path, "w", encoding="utf-8") as f: f.write(str(resultado_policy)) @@ -33,12 +42,9 @@ def verify_lgpd(caminho_repo: str, caminho_dir_lgpd: str, policy_file_path: str, except Exception as e: print(f" - ERRO ao salvar o arquivo 'analise_politica_coleta_de_dados.md': {e}", file=sys.stderr) - - #salvando o julgamento lgpd + #salva o resultado do julgamento lgpd resultado_lgpd = lgpd_check_crew.tasks[1].output - # definindo caminho e nome do arquivo final. - #lgpd_judge_file_path = os.path.join(output_dir_path, "julgamento_lgpd.md") try: with open(lgpd_judge_file_path, "w", encoding="utf-8") as fj: @@ -47,15 +53,22 @@ def verify_lgpd(caminho_repo: str, caminho_dir_lgpd: str, policy_file_path: str, except Exception as e: print(f" - ERRO ao salvar o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) - # fim - # Verificação dos resultados da política de coleta de dados do provedor e model utilizados + #retorna o resultado do julgamento return verify_result_judgement(lgpd_judge_file_path) def verify_result_judgement(lgpd_judge_file_path) -> bool: + """ + Verifica o resultado do julgamento LGPD no arquivo gerado. + + Args: + lgpd_judge_file_path: Caminho do arquivo de julgamento LGPD + + Returns: + bool: True se aprovado ('sim'), False se reprovado ('não') + """ try: with open(lgpd_judge_file_path, "r", encoding="utf-8") as julgamento: - #print(julgamento.readline()) for linha in julgamento: linha_clean = linha.strip().lower() @@ -72,6 +85,16 @@ def verify_result_judgement(lgpd_judge_file_path) -> bool: print(f" - ERRO ao ler o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) def verifica_se_existe_analise_lgpd(policy_file_path, lgpd_judge_file_path) -> bool: + """ + Verifica se já existe uma análise LGPD para o provedor e modelo atuais. + + Args: + policy_file_path: Caminho do arquivo de análise de política + lgpd_judge_file_path: Caminho do arquivo de julgamento LGPD + + Returns: + bool: True se análise já existe para o provedor/modelo atual, False caso contrário + """ provider = os.getenv("AI_PROVIDER").lower() model = os.getenv("AI_MODEL").lower() @@ -82,7 +105,6 @@ def verifica_se_existe_analise_lgpd(policy_file_path, lgpd_judge_file_path) -> b if(os.path.exists(policy_file_path) and os.path.exists(lgpd_judge_file_path)): try: with open(policy_file_path, "r", encoding="utf-8") as f: - #print(julgamento.readline()) for linha in f: linha_clean = linha.strip().lower() diff --git a/codewise_lib/notificacao_gestor.py b/codewise_lib/notificacao_gestor.py index 64b8f33..50eea2d 100644 --- a/codewise_lib/notificacao_gestor.py +++ b/codewise_lib/notificacao_gestor.py @@ -9,6 +9,15 @@ def enviar_telegram(mensagem: str) -> bool: + """ + Envia uma mensagem via Telegram Bot API. + + Args: + mensagem: Texto da mensagem que será enviada + + Returns: + bool: True se enviado, False caso contrário + """ telegram_token = os.getenv("TELEGRAM_BOT_TOKEN") telegram_chat_id = os.getenv("TELEGRAM_CHAT_ID") @@ -40,52 +49,57 @@ def enviar_telegram(mensagem: str) -> bool: def processar_avaliacao_e_notificar(caminho_arquivo: str, email_dev: str, repo_path: str) -> bool: + """ + Processa o arquivo de avaliação de código e envia notificação ao gestor. + + Args: + caminho_arquivo: Caminho do arquivo de avaliação gerado + email_dev: Email do desenvolvedor avaliado + repo_path: Caminho do repositório Git + + Returns: + bool: True se notificação for enviada com sucesso, False caso contrário + """ try: - # Lê o arquivo linha por linha nota = 0.0 justificativa_linhas = [] capturando_breakdown = False with open(caminho_arquivo, 'r', encoding='utf-8') as f: for linha in f: - # Limpa a linha linha_clean = linha.strip() - # Procura pela nota + #varre o arquivo gerado procurando a nota final if 'nota final' in linha_clean.lower(): - # Remove caracteres especiais linha_limpa = re.sub(r'[*_#>`~]', '', linha_clean) linha_limpa = linha_limpa.strip() - # Extrai o número + #pega a nota nota_match = re.search(r'(\d+\.?\d*)', linha_limpa) if nota_match: nota = float(nota_match.group(1)) print(f" ✓ Nota encontrada: {nota}/10", file=sys.stderr) - # Procura pelo início do breakdown + #varre o arquivo procurando o breakdown de pontos if 'breakdown de pontos' in linha_clean.lower(): capturando_breakdown = True continue - # Para de capturar quando encontrar "Justificativa detalhada" + #aborta a captura ao encontrar a seção de 'justificativa detalhada' no arquivo if capturando_breakdown and 'justificativa detalhada' in linha_clean.lower(): break - # Captura linhas do breakdown até "Justificativa" if capturando_breakdown: # Remove caracteres especiais linha_limpa = re.sub(r'[*_#>`~]', '', linha_clean) linha_limpa = linha_limpa.strip() if linha_limpa: - # Adiciona a linha com quebra de linha justificativa_linhas.append(linha_limpa) - # Monta a justificativa com quebras de linha + #justificativa final da nota justificativa = '\n'.join(justificativa_linhas) - # Limita a 4000 caracteres para o Telegram if len(justificativa) > 4000: justificativa = justificativa[:3997] + "..." @@ -95,14 +109,12 @@ def processar_avaliacao_e_notificar(caminho_arquivo: str, email_dev: str, repo_p print(f" ⚠️ Justificativa não encontrada", file=sys.stderr) justificativa = "Avaliação concluída." - # Obtém nome do repositório + #nome do repositório repo_nome = os.path.basename(repo_path) - # Prepara mensagem para Telegram - # Nota é de 0 a 10, então ajusta os limites + #visual para a nota emoji_nota = "🟢" if nota >= 8.5 else "🟡" if nota >= 7.0 else "🔴" - # Escapa caracteres especiais do Markdown justificativa_escaped = justificativa.replace('*', '\\*').replace('_', '\\_').replace('[', '\\[').replace('`', '\\`') mensagem = f""" @@ -118,7 +130,7 @@ def processar_avaliacao_e_notificar(caminho_arquivo: str, email_dev: str, repo_p 📅 *Data:* {datetime.now().strftime("%d/%m/%Y %H:%M")} """ - # Envia notificação + #faz o envio pro telegram já formatado telegram_ok = enviar_telegram(mensagem) return telegram_ok diff --git a/codewise_lib/select_llm.py b/codewise_lib/select_llm.py index 71fcd6e..a1b8c44 100644 --- a/codewise_lib/select_llm.py +++ b/codewise_lib/select_llm.py @@ -3,6 +3,19 @@ import sys def create_llm(provider:str, model:str)-> LLM: + """ + Cria e configura uma instância de LLM baseada no provedor especificado. + + Args: + provider: Nome do provedor ('GEMINI', 'OPENAI', 'GROQ', 'COHERE') + model: Nome do modelo a ser utilizado + + Returns: + LLM: Instância configurada do modelo de linguagem + + Raises: + SystemExit: Se a API key não estiver configurada ou houver erro na inicialização + """ if provider == "GEMINI": if not os.getenv("GEMINI_API_KEY"): print("Erro: A variável de ambiente GEMINI_API_KEY não foi definida.") diff --git a/scripts/codewise_review_win.py b/scripts/codewise_review_win.py index 7f715e1..bfd91e5 100644 --- a/scripts/codewise_review_win.py +++ b/scripts/codewise_review_win.py @@ -68,6 +68,15 @@ def run_codewise_mode(mode, repo_path, branch_name): return None def obter_branch_padrao_remota(repo_path): + """ + Obtém o nome da branch padrão do repositório remoto no GitHub. + + Args: + repo_path: Caminho para o repositório Git local + + Returns: + str: Nome da branch padrão (ex: 'main', 'master') ou 'main' como fallback + """ try: remote_url_result = subprocess.check_output(["git", "config", "--get", "remote.origin.url"], cwd=repo_path, text=True, encoding='utf-8').strip() match = re.search(r'github\.com/([^/]+/[^/]+?)(\.git)?$', remote_url_result) @@ -84,11 +93,31 @@ def obter_branch_padrao_remota(repo_path): return "main" def extrair_titulo_valido(texto): + """ + Extrai um título válido no formato Conventional Commits de um texto. + + Args: + texto: Texto contendo o título do commit + + Returns: + str ou None: Título extraído se encontrado (str), None caso contrário + """ match = re.search(r"(feat|fix|refactor|docs):\s.+", texto, re.IGNORECASE) if match: return match.group(0).strip() return None def obter_pr_aberto_para_branch(branch, repo_dir, repo_slug): + """ + Verifica se existe um Pull Request aberto para uma branch específica. + + Args: + branch: Nome da branch a verificar + repo_dir: Diretório do repositório local + repo_slug: Identificador do repositório no formato 'usuario/repo' + + Returns: + int ou None: Número do PR se encontrado (int), None caso contrário + """ try: comando_list = [ "gh", "pr", "list", @@ -106,7 +135,16 @@ def obter_pr_aberto_para_branch(branch, repo_dir, repo_slug): return None def obter_repo_slug(remote_name, repo_path): - """Obtém o slug 'usuario/repo' de um remote específico ('origin' ou 'upstream').""" + """ + Obtém o slug 'usuario/repo' de um remote específico do GitHub. + + Args: + remote_name: Nome do remote ('origin' ou 'upstream') + repo_path: Caminho para o repositório Git local + + Returns: + str ou None: Slug no formato 'usuario/repo' se encontrado (str), None caso contrário + """ try: remote_url = subprocess.check_output( ["git", "config", "--get", f"remote.{remote_name}.url"], @@ -120,7 +158,16 @@ def obter_repo_slug(remote_name, repo_path): return None def verificar_remote_existe(remote_name, repo_path): - """Verifica se um remote com o nome especificado existe.""" + """ + Verifica se um remote com o nome especificado existe no repositório. + + Args: + remote_name: Nome do remote a verificar (ex: 'origin', 'upstream') + repo_path: Caminho para o repositório Git local + + Returns: + bool: True se o remote existe, False caso contrário + """ try: remotes = subprocess.check_output(["git", "remote"], cwd=repo_path, text=True, encoding='utf-8') return remote_name in remotes.split() @@ -131,6 +178,9 @@ def verificar_remote_existe(remote_name, repo_path): # LÓGICA DO COMANDO 'codewise-lint' (PARA PRE-COMMIT) # =================================================================== def main_lint(): + """ + Executa análise rápida pré-commit do código staged. + """ os.environ['PYTHONIOENCODING'] = 'utf-8' repo_path = os.getcwd() try: @@ -168,7 +218,13 @@ def main_lint(): # =================================================================== def run_pr_logic(target_selecionado, pushed_branch): - """Função principal que contém toda a lógica de criação de PR.""" + """ + Função principal que contém toda a lógica de criação de Pull Request. + + Args: + target_selecionado: Nome do remote alvo ('origin' ou 'upstream') + pushed_branch: Nome da branch que está sendo enviada + """ current_branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"], text=True).strip() @@ -310,7 +366,9 @@ def run_pr_logic(target_selecionado, pushed_branch): def main_pr_origin(): - """Ponto de entrada para criar um PR no 'origin'.""" + """ + Ponto de entrada para criar um PR no 'origin'. + """ parser = argparse.ArgumentParser() parser.add_argument("--pushed-branch", required=False, type=str, help="A branch que está sendo enviada.") args = parser.parse_args() @@ -328,7 +386,9 @@ def main_pr_origin(): def main_pr_upstream(): - """Ponto de entrada para criar um PR no 'upstream'.""" + """ + Ponto de entrada para criar um PR no 'upstream'. + """ parser = argparse.ArgumentParser() parser.add_argument("--pushed-branch", required=False, type=str, help="A branch que está sendo enviada.") args = parser.parse_args() @@ -347,7 +407,9 @@ def main_pr_upstream(): def main_pr_interactive(): - """Função interativa para ser chamada manualmente pelo comando 'codewise-pr'.""" + """ + Função interativa para ser chamada manualmente pelo comando 'codewise-pr'. + """ repo_path = os.getcwd() try: @@ -384,6 +446,16 @@ def main_pr_interactive(): def lgpd_check_user_choice(repo_path:str, branch_atual:str): + """ + Verifica a conformidade LGPD e solicita autorização do usuário para envio de dados. + + Args: + repo_path: Caminho para o repositório Git local + branch_atual: Nome da branch atual sendo analisada + + Returns: + bool: True se o usuário autorizou, encerra o programa caso contrário + """ caminho_dir_lgpd = os.path.join(repo_path, "analises-julgamento-lgpd") policy_file_path = os.path.join(caminho_dir_lgpd, "analise_politica_coleta_de_dados.md") lgpd_judge_file_path = os.path.join(caminho_dir_lgpd, "julgamento_lgpd.md") @@ -419,7 +491,6 @@ def lgpd_check_user_choice(repo_path:str, branch_atual:str): # Trecho utilizando biblioteca para receber o input direto do SO! try: - # Se estiver no Windows (seu caso) if sys.platform == 'win32': import msvcrt @@ -428,14 +499,13 @@ def lgpd_check_user_choice(repo_path:str, branch_atual:str): choice = char.upper() print() - # Se estiver no Linux/Mac (Fallback para compatibilidade) else: with open("/dev/tty", "r") as tty: choice = tty.readline().strip().upper() except Exception: choice = input().strip().upper() - #choice = input("* Com base na verificação apresentada acima, você gostaria de continuar com o envio de seus dados para o provedor e modelo de api key escolhido? [S/N]: ").strip().upper() + print() if(choice == "S"): print("-" * 40) diff --git a/scripts/help.py b/scripts/help.py index 7ad2ead..72e416f 100644 --- a/scripts/help.py +++ b/scripts/help.py @@ -1,4 +1,7 @@ def main(): + """ + Exibe a mensagem de ajuda do CodeWise com comandos disponíveis e instruções de uso. + """ print(""" CodeWise CLI diff --git a/scripts/install_hook.py b/scripts/install_hook.py index 1928e11..cace7b6 100644 --- a/scripts/install_hook.py +++ b/scripts/install_hook.py @@ -21,6 +21,15 @@ """ def verificar_remote_existe(remote_name): + """ + Verifica se um remote Git com o nome especificado existe no repositório atual. + + Args: + remote_name: Nome do remote a verificar (ex: 'origin', 'upstream') + + Returns: + bool: True se o remote existe, False caso contrário + """ try: repo_path = os.getcwd() remotes = subprocess.check_output(["git", "remote"], cwd=repo_path, text=True, encoding='utf-8') @@ -29,6 +38,17 @@ def verificar_remote_existe(remote_name): return False def install_hook(hook_name, hook_content, repo_root): + """ + Instala um hook Git no repositório especificado. + + Args: + hook_name: Nome do hook (ex: 'pre-commit', 'pre-push') + hook_content: Conteúdo do script do hook + repo_root: Caminho raiz do repositório + + Returns: + bool: True se instalado com sucesso, False caso contrário + """ hooks_dir = os.path.join(repo_root, '.git', 'hooks') if not os.path.isdir(hooks_dir): print(f"❌ Erro: Diretório de hooks do Git não encontrado em '{hooks_dir}'.", file=sys.stderr) @@ -47,6 +67,10 @@ def install_hook(hook_name, hook_content, repo_root): return False def main(): + """ + Ponto de entrada principal para instalação de hooks Git do CodeWise. + Permite instalar hooks de pre-commit e/ou pre-push, --all para instalar ambos juntos. + """ parser = argparse.ArgumentParser(description="Instalador de hooks do CodeWise.") parser.add_argument('--commit', action='store_true', help='Instala o hook pre-commit.') parser.add_argument('--push', action='store_true', help='Instala o hook pre-push.') From 8ec22ced8e0bc6e7a58da1e64d127ace4c158725 Mon Sep 17 00:00:00 2001 From: Matheus Magnago Date: Tue, 2 Dec 2025 11:51:53 -0300 Subject: [PATCH 30/36] optimizing --- codewise_lib/cw_runner.py | 9 ++----- codewise_lib/lgpd.py | 33 +++++++++++------------ scripts/codewise_review_win.py | 49 ++++++++++++++++++---------------- 3 files changed, 44 insertions(+), 47 deletions(-) diff --git a/codewise_lib/cw_runner.py b/codewise_lib/cw_runner.py index e45d569..28810f1 100644 --- a/codewise_lib/cw_runner.py +++ b/codewise_lib/cw_runner.py @@ -21,16 +21,11 @@ def executar(self, caminho_repo: str, nome_branch: str, modo: str): lgpd_judge_file_path = os.path.join(caminho_dir_lgpd, "julgamento_lgpd.md") if(modo == 'lgpd_verify'): - if(verifica_se_existe_analise_lgpd(policy_file_path, lgpd_judge_file_path)): - print("\nA análise e verificação LGPD já foi feita anteriormente para este mesmo provedor e modelo api key. Por conta disso, não será necessário efetuá-la novamente! \n",file=sys.stderr) - print("Verificando o julgamento da análise existente...\n" ,file=sys.stderr) - verify_result_judgement(lgpd_judge_file_path) - else: + if not(verifica_se_existe_analise_lgpd(policy_file_path, lgpd_judge_file_path)): print("Iniciando análise e julgamento LGPD...",file=sys.stderr) - verify_lgpd(caminho_repo, caminho_dir_lgpd, policy_file_path, lgpd_judge_file_path) + verify_lgpd(caminho_dir_lgpd, policy_file_path, lgpd_judge_file_path) return 0 - contexto_para_ia = "" if modo == 'lint': diff --git a/codewise_lib/lgpd.py b/codewise_lib/lgpd.py index ed1beab..65869db 100644 --- a/codewise_lib/lgpd.py +++ b/codewise_lib/lgpd.py @@ -4,7 +4,7 @@ from dotenv import load_dotenv from .crew import Codewise -def verify_lgpd(caminho_repo: str, caminho_dir_lgpd: str, policy_file_path: str, lgpd_judge_file_path: str) -> bool: +def verify_lgpd(caminho_dir_lgpd: str, policy_file_path: str, lgpd_judge_file_path: str): # Tentativa de colocar a analise lgpd para rodar antes do envio dos dados sensiveis # instancia sem passar o commit como contexto codewise_instance = Codewise() @@ -29,25 +29,22 @@ def verify_lgpd(caminho_repo: str, caminho_dir_lgpd: str, policy_file_path: str, try: with open(policy_file_path, "w", encoding="utf-8") as f: f.write(str(resultado_policy)) - print(f" - Arquivo 'analise_politica_coleta_de_dados.md' salvo com sucesso em '{caminho_dir_lgpd}'.", file=sys.stderr) + print(f"Arquivo 'analise_politica_coleta_de_dados.md' salvo com sucesso em '{caminho_dir_lgpd}'.", file=sys.stderr) + except FileNotFoundError as e: + print(f"❌ ERRO - Arquivo 'analise_politica_coleta_de_dados.md' não existe: {e}", file=sys.stderr) except Exception as e: - print(f" - ERRO ao salvar o arquivo 'analise_politica_coleta_de_dados.md': {e}", file=sys.stderr) - - - #salvando o julgamento lgpd - resultado_lgpd = lgpd_check_crew.tasks[1].output + print(f"❌ ERRO - Ao salvar o arquivo 'analise_politica_coleta_de_dados.md': {e}", file=sys.stderr) - # definindo caminho e nome do arquivo final. - #lgpd_judge_file_path = os.path.join(output_dir_path, "julgamento_lgpd.md") + resultado_lgpd = lgpd_check_crew.tasks[1].output try: with open(lgpd_judge_file_path, "w", encoding="utf-8") as fj: fj.write(str(resultado_lgpd)) - print(f" - Arquivo 'julgamento_lgpd.md' salvo com sucesso em '{caminho_dir_lgpd}'.", file=sys.stderr) + print(f"✅ Arquivo 'julgamento_lgpd.md' salvo com sucesso em '{caminho_dir_lgpd}'.", file=sys.stderr) + except FileNotFoundError as e: + print(f"❌ ERRO - Arquivo 'julgamento_lgpd.md' não existe: {e}", file=sys.stderr) except Exception as e: - print(f" - ERRO ao salvar o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) - - # fim + print(f"❌ ERRO - Ao salvar o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) # Verificação dos resultados da política de coleta de dados do provedor e model utilizados return verify_result_judgement(lgpd_judge_file_path) @@ -68,8 +65,10 @@ def verify_result_judgement(lgpd_judge_file_path) -> bool: return True if (linha_clean == "não"): return False + except FileNotFoundError as e: + print(f"❌ ERRO - Arquivo 'julgamento_lgpd.md' não existe: {e}", file=sys.stderr) except Exception as e: - print(f" - ERRO ao ler o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) + print(f"❌ ERRO - ao ler o arquivo 'julgamento_lgpd.md': {e}", file=sys.stderr) def verifica_se_existe_analise_lgpd(policy_file_path, lgpd_judge_file_path) -> bool: @@ -82,8 +81,6 @@ def verifica_se_existe_analise_lgpd(policy_file_path, lgpd_judge_file_path) -> b if(os.path.exists(policy_file_path) and os.path.exists(lgpd_judge_file_path)): try: with open(policy_file_path, "r", encoding="utf-8") as f: - #print(julgamento.readline()) - for linha in f: linha_clean = linha.strip().lower() @@ -94,7 +91,9 @@ def verifica_se_existe_analise_lgpd(policy_file_path, lgpd_judge_file_path) -> b return True if(not linha): return False + except FileNotFoundError as e: + print(f"❌ ERRO - Arquivo 'analise_politica_coleta_de_dados.md' não existe: {e}", file=sys.stderr) except Exception as e: - print(f" - ERRO ao ler o arquivo 'analise_politica_coleta_de_dados.md': {e}", file=sys.stderr) + print(f"❌ ERRO - ao ler o arquivo 'analise_politica_coleta_de_dados.md': {e}", file=sys.stderr) else: return False \ No newline at end of file diff --git a/scripts/codewise_review_win.py b/scripts/codewise_review_win.py index 7f715e1..732a54b 100644 --- a/scripts/codewise_review_win.py +++ b/scripts/codewise_review_win.py @@ -37,8 +37,8 @@ def run_codewise_mode(mode, repo_path, branch_name): stdin=subprocess.DEVNULL ) - if result.stderr: - print(result.stderr, file=sys.stderr) + #if result.stderr: + #print(result.stderr, file=sys.stderr) return result.stdout.strip() except subprocess.CalledProcessError as e: @@ -383,16 +383,19 @@ def main_pr_interactive(): run_pr_logic(target_selecionado=target_selecionado, pushed_branch=current_branch) +# =================================================================== +# LÓGICA DO MODO INTERNO LGPD_VERIFY QUE CAPTURA INPUT DO USUÁRIO +# =================================================================== + + def lgpd_check_user_choice(repo_path:str, branch_atual:str): caminho_dir_lgpd = os.path.join(repo_path, "analises-julgamento-lgpd") - policy_file_path = os.path.join(caminho_dir_lgpd, "analise_politica_coleta_de_dados.md") lgpd_judge_file_path = os.path.join(caminho_dir_lgpd, "julgamento_lgpd.md") run_codewise_mode("lgpd_verify", repo_path, branch_atual) - - if not os.path.exists(policy_file_path): - sys.exit("Erro: O arquivo de política de dados não existe! Execute novamente para a análise ser feita.") + if not os.path.exists(caminho_dir_lgpd): + sys.exit("Erro: A pasta analises-julgamento-lgpd não existe! Execute novamente para efetuar a verificação LGPD.") try: with open(lgpd_judge_file_path, "r", encoding="utf-8") as f: @@ -418,24 +421,21 @@ def lgpd_check_user_choice(repo_path:str, branch_atual:str): print() # Trecho utilizando biblioteca para receber o input direto do SO! - try: - # Se estiver no Windows (seu caso) - if sys.platform == 'win32': - import msvcrt - - print("\nCom base na verificação apresentada acima, você gostaria de continuar com o envio de seus dados para o provedor e modelo de api key escolhido? [S/N]:") - char = msvcrt.getwche() - choice = char.upper() - print() + # Se estiver no Windows + if sys.platform == 'win32': + import msvcrt - # Se estiver no Linux/Mac (Fallback para compatibilidade) - else: - with open("/dev/tty", "r") as tty: - choice = tty.readline().strip().upper() - except Exception: - choice = input().strip().upper() - - #choice = input("* Com base na verificação apresentada acima, você gostaria de continuar com o envio de seus dados para o provedor e modelo de api key escolhido? [S/N]: ").strip().upper() + print("\nCom base na verificação apresentada acima, você gostaria de continuar com o envio de seus dados para o provedor e modelo de api key escolhido? [S/N]:") + char = msvcrt.getwche() + choice = char.upper() + print() + # Se estiver no Linux/Mac + elif(sys.platform == 'linux' or sys.platform == 'darwin'): + with open("/dev/tty", "r") as tty: + choice = tty.readline().strip().upper() + else: + choice = input("* Com base na verificação apresentada acima, você gostaria de continuar com o envio de seus dados para o provedor e modelo de api key escolhido? [S/N]: ").strip().upper() + print() if(choice == "S"): print("-" * 40) @@ -452,6 +452,9 @@ def lgpd_check_user_choice(repo_path:str, branch_atual:str): print() print("-" * 40) sys.exit(0) + except FileNotFoundError as e: + print(f"❌ ERRO - Arquivo 'julgamento_lgpd.md' não existe: {e}", file=sys.stderr) + sys.exit(1) except Exception as e: print(f"Erro em obter a autorização do usuário: {e}", file=sys.stderr) sys.exit(1) From bebc22f1475f0ae2eeea1dd0ae269dd738a74345 Mon Sep 17 00:00:00 2001 From: Matheus Magnago Date: Tue, 2 Dec 2025 20:05:07 -0300 Subject: [PATCH 31/36] Ajusting judge task and fixing code_review output --- codewise_lib/config/tasks.yaml | 6 ++++-- codewise_lib/crew.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/codewise_lib/config/tasks.yaml b/codewise_lib/config/tasks.yaml index be5983e..449acac 100644 --- a/codewise_lib/config/tasks.yaml +++ b/codewise_lib/config/tasks.yaml @@ -57,9 +57,11 @@ policy_analytics: lgpd_judging: description: > - Com base nas leis LGPD e no relatório da analise da documentação, + Com base nas leis LGPD e no relatório da analise da documentação fornecida pela tarefa anterior, determine se o provedor da api key está respeitando ou não as leis LGPD e justifique o julgamento apontando as provas, por meio de um relatório, onde a primeira linha **exatamente na linha 1** do arquivo é a resposta antecipada (sim ou não). - Lembre-se de usar a ferramenta para buscar as leis LGPD do site oficial (gov.br). Além disso, coloque a conclusão sempre no final do arquivo. + Lembre-se de usar a ferramenta para buscar as leis LGPD do site oficial (gov.br) e de utilizar o contexto (análise) da tarefa anterior. Além disso, coloque a conclusão sempre no final do arquivo. + Monta o relatório de maneira CLEAN, e bem separado por tópicos (Breve introdução --> Análise da Conformidade com a LGPD --> Conclusão --> artigos LGPD utilizados --> referências). + Após os tópicos: Referências, coloque em uma linha única, *fim referências*, para indicar o fim da conclusão. expected_output: > Documento julgamento_lgpd.md com as provas ou artigos utilizados para determinar a decisão tomada. agent: lgpd_judge diff --git a/codewise_lib/crew.py b/codewise_lib/crew.py index d87a19c..e0ec210 100644 --- a/codewise_lib/crew.py +++ b/codewise_lib/crew.py @@ -71,7 +71,7 @@ def dataCollect_policy_analytics(self) -> Agent: return Agent(config=self.agents def lgpd_judge(self) -> Agent: return Agent(config=self.agents_config['lgpd_judge'], llm=self.llm, tools=[self.web_search_tool], verbose = False) @agent - def code_reviewer(self) -> Agent: return Agent(config=self.agents_config['code_reviewer'], llm=self.llm, verbose=True) + def code_reviewer(self) -> Agent: return Agent(config=self.agents_config['code_reviewer'], llm=self.llm, verbose=False) #definição das tarefas disponíveis para cada agente @task @@ -114,7 +114,7 @@ def task_policy(self) -> Task: @task def task_judging(self) -> Task: cfg = self.tasks_config['lgpd_judging'] - return Task(description=cfg['description'], expected_output=cfg['expected_output'], agent=self.lgpd_judge()) + return Task(description=cfg['description'], expected_output=cfg['expected_output'], agent=self.lgpd_judge(), context=[self.task_policy()]) @task def task_code_review(self) -> Task: From 7c419ba775374d6b1d24bb4a3a3724a98df6fbc4 Mon Sep 17 00:00:00 2001 From: Matheus Magnago Date: Tue, 2 Dec 2025 20:19:22 -0300 Subject: [PATCH 32/36] fixing user input regex --- scripts/codewise_review_win.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/codewise_review_win.py b/scripts/codewise_review_win.py index ff80ce1..ff6ac63 100644 --- a/scripts/codewise_review_win.py +++ b/scripts/codewise_review_win.py @@ -479,7 +479,7 @@ def lgpd_check_user_choice(repo_path:str, branch_atual:str): print("") # Pegar apenas a conclusao do arquivo e mostrar ao usuario no cmd - conclusao = re.search(r"(?i)^#+\s*Conclusão\s*\n+(.*)", content_resume, re.MULTILINE | re.DOTALL) + conclusao = re.search(r"(?i)^#+\s*Conclusão\s*\n+(.*?)\s*fim referência", content_resume, re.MULTILINE | re.DOTALL) if(conclusao): conclusao_content = conclusao.group(1).strip() From 63ac2606a7ce6b3c5dd827ea87db285b884c65c9 Mon Sep 17 00:00:00 2001 From: Matheus de Oliveira Magnago <103756709+magnagomatheus@users.noreply.github.com> Date: Tue, 2 Dec 2025 20:43:50 -0300 Subject: [PATCH 33/36] README update Update README to improve clarity and fix formatting issues. --- README.md | 257 +++++++++++++++++++++++++++++------------------------- 1 file changed, 140 insertions(+), 117 deletions(-) diff --git a/README.md b/README.md index 19f9063..04d00e5 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,45 @@ -# 🧠 **CodeWise** +# CodeWise -> **Ferramenta instalável via `pip` que usa IA para analisar o código e automatizar a documentação de Pull Requests através de hooks do Git.** +Ferramenta instalavel via `pip` que utiliza IA para analisar codigo e automatizar a documentacao de Pull Requests atraves de hooks do Git. --- -## 🚀 **Funcionalidades Principais** +## Funcionalidades -- 🏷️ **Geração de Título:** Cria títulos de PR claros e concisos seguindo o padrão *Conventional Commits*. -- 📝 **Geração de Descrição:** Escreve descrições detalhadas baseadas nas alterações do código. -- 🧩 **Análise Técnica:** Posta um comentário no PR com um resumo executivo de melhorias de arquitetura, aderência a princípios S.O.L.I.D. e outros pontos de qualidade. -- 🔁 **Automação com Hooks:** Integra-se ao seu fluxo de trabalho Git para rodar automaticamente a cada `git commit` e `git push`. -- 🤖 **Flexibilidade de IA:** Escolha qual provedor de IA usar (`Cohere`, `Google Gemini`, `Groq`, `OpenAI`) através de uma simples configuração. -- 🔒 **Verificação de Privacidade (LGPD):** Analisa automaticamente a política de coleta de dados do provedor de IA antes de enviar o seu código. +- **Geracao de Titulo:** Cria titulos de PR claros e concisos seguindo o padrao Conventional Commits. +- **Geracao de Descricao:** Escreve descricoes detalhadas baseadas nas alteracoes do codigo. +- **Analise Tecnica:** Posta um comentario no PR com resumo executivo de melhorias de arquitetura, aderencia a principios S.O.L.I.D. e outros pontos de qualidade. +- **Automacao com Hooks:** Integra-se ao fluxo de trabalho Git para rodar automaticamente a cada `git commit` e `git push`. +- **Flexibilidade de IA:** Escolha qual provedor de IA usar (`Cohere`, `Google Gemini`, `Groq`, `OpenAI`) atraves de configuracao. +- **Verificacao de Privacidade (LGPD):** Analisa automaticamente a politica de coleta de dados do provedor de IA antes de enviar o codigo. +- **Avaliacao de Codigo:** Gera relatorios de avaliacao com nota e justificativa detalhada. +- **Notificacao via Telegram:** Envia avaliacoes automaticamente para gestores via Telegram Bot API. --- -## ⚙️ **Guia de Instalação** +## Pre-requisitos -Siga os passos abaixo para instalar e configurar o **CodeWise** em qualquer repositório. +Antes de comecar, garanta que voce tenha instaladas as seguintes ferramentas: ---- - -### 🧩 **Passo 1 — Pré-requisitos** - -Antes de começar, garanta que você tenha instaladas as seguintes ferramentas: - -1. **Python** (versão 3.11 ou superior) -2. **Git** +1. **Python** (versao 3.11 ou superior) +2. **Git** 3. **GitHub CLI (`gh`)** -> Após instalar a CLI do GitHub ([https://cli.github.com](https://cli.github.com)), execute: -> ```bash -> gh auth login -> ``` -> Faça login na sua conta — este passo é necessário apenas uma vez por computador. +Apos instalar a CLI do GitHub (https://cli.github.com), execute: ---- - -### 🧱 **Passo 2 — Configurando Seu Repositório** +```bash +gh auth login +``` -> O ideal é sempre criar um **ambiente virtual na pasta raiz** do novo repositório para evitar conflitos de dependências. +Faca login na sua conta. Este passo e necessario apenas uma vez por computador. --- -#### 🔹 2.1 Criar e Ativar o Ambiente Virtual +## Instalacao + +### 1. Criar e Ativar o Ambiente Virtual -**Crie o ambiente virtual** (dentro da raiz do repositório onde está a pasta `.git`): +Crie o ambiente virtual na raiz do repositorio onde esta a pasta `.git`: ```bash # Windows @@ -55,9 +49,7 @@ py -m venv .venv python3 -m venv .venv ``` -> 💡 O nome `.venv` é apenas uma convenção — você pode usar outro nome se quiser. - -**Ative o ambiente:** +Ative o ambiente: ```bash # Windows (PowerShell) @@ -67,16 +59,13 @@ python3 -m venv .venv source .venv/bin/activate ``` -> ⚠️ Se ocorrer erro de política de execução no PowerShell, rode: -> ```bash -> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -> ``` - -Você saberá que funcionou quando o nome `(.venv)` aparecer no início da linha do terminal. +Se ocorrer erro de politica de execucao no PowerShell, rode: ---- +```bash +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser +``` -#### 🔹 2.2 Instalar a Ferramenta CodeWise +### 2. Instalar o CodeWise Com o ambiente virtual ativo, instale o pacote: @@ -84,134 +73,168 @@ Com o ambiente virtual ativo, instale o pacote: pip install codewise-lib ``` -> ⏳ A primeira instalação pode demorar um pouco. -> Após concluir, confirme se está tudo certo com: -> ```bash -> codewise-help -> ``` +Apos concluir, confirme se esta tudo certo com: + +```bash +codewise-help +``` --- -#### 🔹 2.3 Configurar a Chave da API (`.env`) +## Configuracao do Arquivo .env -Para que a IA funcione, configure suas chaves de API e o provedor desejado. +Na raiz do projeto, crie um arquivo `.env` com as seguintes variaveis: -1. **Na raiz do projeto**, crie um arquivo `.env`: +```ini +# PROVEDOR DE IA +# Opcoes disponiveis: "COHERE", "GROQ", "GEMINI", "OPENAI" +AI_PROVIDER="GEMINI" + +# MODELO ESPECIFICO +# Exemplos: "gemini-2.0-flash", "gpt-4o-mini", "command-r-plus", "llama-3.1-70b-versatile" +AI_MODEL=gemini-2.0-flash + +# CHAVES DE API DOS PROVEDORES +# Configure a chave correspondente ao AI_PROVIDER escolhido +COHERE_API_KEY=sua_chave_cohere_aqui +GROQ_API_KEY=sua_chave_groq_aqui +GEMINI_API_KEY=sua_chave_gemini_aqui +OPENAI_API_KEY=sua_chave_openai_aqui + +# TELEGRAM (opcional - para notificacoes de avaliacao) +TELEGRAM_BOT_TOKEN=seu_token_do_bot_telegram +TELEGRAM_CHAT_ID=seu_chat_id_telegram +``` -```bash -# Windows -notepad .env +**Importante:** Adicione o arquivo `.env` ao `.gitignore` para evitar expor suas chaves secretas. -# Linux/WSL -touch .env && nano .env -``` +--- -2. **Adicione o conteúdo abaixo e insira suas chaves:** +## Chave OpenAI para Embedding (Obrigatorio) -```ini -# 1. ESCOLHA O PROVEDOR DE IA -# Opções disponíveis: "COHERE", "GROQ", "GEMINI", "OPENAI" -AI_PROVIDER="GEMINI" # -> maiúsculo!!! - -# 2. ESCOLHA O MODELO ESPECÍFICO -# Ex.: "gemini-2.0-flash", "gpt-4o-mini" -AI_MODEL=gemini-2.0-flash # -> *sem* aspas - -# 3. COLOQUE SUA(S) CHAVE(S) DE API -# A ferramenta usará a chave correta com base no AI_PROVIDER -COHERE_API_KEY=sua_chave_cohere_api_aqui -GROQ_API_KEY=sua_chave_groq_api_aqui -GEMINI_API_KEY=sua_chave_gemini_api_aqui -OPENAI_API_KEY=sua_chave_openai_api_aqui -``` +O CodeWise utiliza o CrewAI com ferramentas que dependem de embedding para busca semantica. Por isso, **a chave `OPENAI_API_KEY` e obrigatoria** no arquivo `.env`, mesmo que voce utilize outro provedor de IA (Gemini, Groq, Cohere) como modelo principal. -> ⚠️ **Importante:** -> Adicione o arquivo `.env` ao `.gitignore` para evitar expor suas chaves secretas no GitHub. +A OpenAI e utilizada internamente pelo CrewAI Tools para realizar operacoes de embedding. Sem essa chave configurada, as ferramentas de analise nao funcionarao corretamente. --- -### 🔸 **Nota Importante sobre Remotes** +## Configuracao do Telegram (Opcional) + +Para receber notificacoes de avaliacao de codigo via Telegram: -A ferramenta CodeWise espera que seus remotes sigam a convenção padrão do GitHub: +1. **Criar um Bot no Telegram:** + - Abra o Telegram e busque por `@BotFather` + - Envie o comando `/newbot` e siga as instrucoes + - Copie o token gerado para `TELEGRAM_BOT_TOKEN` -- **origin** → aponta para o **seu fork pessoal** do repositório -- **upstream** → (opcional) aponta para o **repositório principal** +2. **Obter o Chat ID:** + - Inicie uma conversa com seu bot + - Acesse `https://api.telegram.org/bot/getUpdates` + - Localize o campo `chat.id` na resposta JSON + - Copie o valor para `TELEGRAM_CHAT_ID` -> 🧠 Dica: -> Se o repositório for novo, execute um push inicial com: -> ```bash -> git push --no-verify -> ``` -> Isso garante que o `gh` funcione corretamente na criação dos Pull Requests. +3. **Adicionar ao .env:** + +```ini +TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz +TELEGRAM_CHAT_ID=987654321 +``` + +As notificacoes incluem: desenvolvedor avaliado, repositorio, nota, resumo da avaliacao e data. --- -#### 🔹 2.4 Ativar a Automação no Repositório +## Ativar a Automacao no Repositorio -Na raiz do projeto (onde está a pasta `.git`), execute **uma única vez**: +Na raiz do projeto (onde esta a pasta `.git`), execute uma unica vez: ```bash codewise-init --all ``` -Esse comando adicionará automaticamente os hooks `pre-commit` e `pre-push`. - -Se o seu repositório tiver um `upstream`, o instalador perguntará: - -``` -Um remote 'upstream' foi detectado. -Qual deve ser o comportamento padrão do 'git push'? -1: Criar Pull Request no 'origin' (seu fork) -2: Criar Pull Request no 'upstream' (projeto principal) -Escolha o padrão (1 ou 2): -``` +Esse comando adicionara automaticamente os hooks `pre-commit` e `pre-push`. -> Sua escolha será salva no hook — não será necessário configurá-la novamente. -> Se não houver `upstream`, o padrão será `origin`. +Se o seu repositorio tiver um `upstream`, o instalador perguntara qual deve ser o comportamento padrao do `git push` para criacao de Pull Requests. --- -## 🧰 **Usando o CodeWise** +## Comandos Disponiveis -Com tudo configurado, você pode usar os comandos **`codewise-lint`** e **`codewise-pr`** manualmente ou automaticamente pelos hooks. +| Comando | Descricao | +|---------|-----------| +| `codewise-init --all` | Instala os hooks pre-commit e pre-push | +| `codewise-init --commit` | Instala apenas o hook pre-commit | +| `codewise-init --push` | Instala apenas o hook pre-push | +| `codewise-pr` | Analisa commits e cria/atualiza PR com IA | +| `codewise-pr-origin` | Cria PR no remote origin | +| `codewise-pr-upstream` | Cria PR no remote upstream | +| `codewise-lint` | Analisa arquivos staged antes do commit | +| `codewise-help` | Exibe ajuda e comandos disponiveis | --- -### 🔸 **Fluxo de Uso** +## Fluxo de Uso + +1. **Adicione suas alteracoes:** -#### 1️⃣ Adicione suas alterações ```bash git add . ``` -> 💡 Use `codewise-lint` antes do commit para revisar seu código. ---- +2. **Faca o commit:** -#### 2️⃣ Faça o commit ```bash git commit -m "implementa novo recurso" ``` -> O **hook `pre-commit`** será ativado e executará o `codewise-lint` automaticamente. ---- +O hook `pre-commit` sera ativado e executara o `codewise-lint` automaticamente. + +3. **Envie para o GitHub:** -#### 3️⃣ Envie para o GitHub ```bash git push ``` -> O **hook `pre-push`** ativará o `codewise-pr`, que: -> - Perguntará para qual remote enviar (caso exista um `upstream`); -> - Criará ou atualizará o Pull Request com **título, descrição e análise técnica** gerados pela IA. + +O hook `pre-push` ativara o `codewise-pr`, que criara ou atualizara o Pull Request com titulo, descricao e analise tecnica gerados pela IA. --- -## 🛡️ **Verificação de Privacidade e LGPD** +## Nota sobre Remotes + +A ferramenta CodeWise espera que seus remotes sigam a convencao padrao do GitHub: + +- **origin:** aponta para o seu fork pessoal do repositorio +- **upstream:** (opcional) aponta para o repositorio principal -Antes de qualquer envio de código, o `codewise-lib` realiza uma **verificação de privacidade automática**. -O objetivo é garantir que o provedor de IA configurado no `.env` possua políticas compatíveis com a **LGPD**, assegurando a proteção dos seus dados e da sua base de código. +Se o repositorio for novo, execute um push inicial com: + +```bash +git push --no-verify +``` + +Isso garante que o `gh` funcione corretamente na criacao dos Pull Requests. --- -### ✅ **Tudo pronto!** -Seu repositório já está com o CodeWise ativo. +## Verificacao de Privacidade e LGPD + +Antes de qualquer envio de codigo, o CodeWise realiza uma verificacao de privacidade automatica. O objetivo e garantir que o provedor de IA configurado no `.env` possua politicas compativeis com a LGPD, assegurando a protecao dos seus dados e da sua base de codigo. + +--- + +## Dependencias + +- crewai >= 0.201.1 +- crewai-tools >= 0.76.0 +- python-dotenv >= 1.1.1 +- PyYAML >= 6.0.3 +- litellm >= 1.74.9 +- qdrant-client >= 1.15.1 +- requests >= 2.32.3 + +--- + +✅ Tudo pronto! + +Seu repositório já está com o CodeWise ativo. Para usar em outro repositório, basta repetir os passos acima. From 7a197a1730e6aa19f53b03c6f6c2b7f17e6e4cf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Ren=C3=A3?= Date: Wed, 3 Dec 2025 16:38:47 -0300 Subject: [PATCH 34/36] =?UTF-8?q?removendo=20tool=20n=C3=A3o=20mais=20util?= =?UTF-8?q?izada?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codewise_lib/crew.py | 3 - codewise_lib/tools/__init__.py | 3 - codewise_lib/tools/git_analysis_tool.py | 230 ------------------------ 3 files changed, 236 deletions(-) delete mode 100644 codewise_lib/tools/__init__.py delete mode 100644 codewise_lib/tools/git_analysis_tool.py diff --git a/codewise_lib/crew.py b/codewise_lib/crew.py index e0ec210..535a9cb 100644 --- a/codewise_lib/crew.py +++ b/codewise_lib/crew.py @@ -11,7 +11,6 @@ WebsiteSearchTool ) -from .tools.git_analysis_tool import GitAnalysisTool, GitBlameAnalysisTool @CrewBase class Codewise: @@ -32,8 +31,6 @@ def __init__(self, commit_message: str = ""): #tools iniciais self.web_search_tool = WebsiteSearchTool() - self.git_analysis_tool = GitAnalysisTool() - self.git_blame_tool = GitBlameAnalysisTool() #carregamento de configurações de agentes e tarefas base_dir = os.path.dirname(os.path.abspath(__file__)) diff --git a/codewise_lib/tools/__init__.py b/codewise_lib/tools/__init__.py deleted file mode 100644 index dc35b78..0000000 --- a/codewise_lib/tools/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .git_analysis_tool import GitAnalysisTool, GitBlameAnalysisTool - -__all__ = ['GitAnalysisTool', 'GitBlameAnalysisTool'] diff --git a/codewise_lib/tools/git_analysis_tool.py b/codewise_lib/tools/git_analysis_tool.py deleted file mode 100644 index 719832b..0000000 --- a/codewise_lib/tools/git_analysis_tool.py +++ /dev/null @@ -1,230 +0,0 @@ -import os -import subprocess -import tempfile -from typing import Optional - - -class GitAnalysisTool: - """Ferramenta para analisar mudanças do Git usando git log e git blame.""" - - name: str = "Git Analysis Tool" - description: str = ( - "Ferramenta para analisar mudanças do Git usando git log e git blame. " - "Retorna informações detalhadas sobre commits, autores, e mudanças no código." - ) - - def run(self, repo_path: str, author_email: Optional[str] = None, - commits_limit: int = 10, branch: str = "HEAD") -> str: - """ - Analisa o repositório Git e retorna informações sobre commits e mudanças. - - Args: - repo_path: Caminho para o repositório Git - author_email: Email do autor para filtrar (opcional) - commits_limit: Número de commits a analisar - branch: Branch a ser analisada - """ - try: - # Verifica se é um repositório Git válido - if not os.path.exists(os.path.join(repo_path, '.git')): - return f"Erro: {repo_path} não é um repositório Git válido." - - result = [] - result.append("=" * 80) - result.append("ANÁLISE DE MUDANÇAS GIT") - result.append("=" * 80) - result.append("") - - # Comando git log com informações detalhadas - git_log_cmd = [ - 'git', '-C', repo_path, 'log', - f'-{commits_limit}', - '--pretty=format:%H|%an|%ae|%ad|%s', - '--date=iso', - '--numstat', - branch - ] - - if author_email: - git_log_cmd.extend(['--author', author_email]) - - log_output = subprocess.check_output( - git_log_cmd, - stderr=subprocess.STDOUT, - text=True - ) - - # Processa a saída do git log - commits_data = self._parse_git_log(log_output) - - for commit in commits_data: - result.append(f"Commit: {commit['hash'][:8]}") - result.append(f"Autor: {commit['author']} <{commit['email']}>") - result.append(f"Data: {commit['date']}") - result.append(f"Mensagem: {commit['message']}") - result.append("") - - if commit['files']: - result.append("Arquivos modificados:") - for file_info in commit['files']: - result.append(f" {file_info['additions']:>4}+ {file_info['deletions']:>4}- {file_info['file']}") - result.append("") - - # Git diff para ver as mudanças reais - try: - diff_cmd = ['git', '-C', repo_path, 'show', - '--pretty=format:', '--unified=3', commit['hash']] - diff_output = subprocess.check_output( - diff_cmd, - stderr=subprocess.STDOUT, - text=True - ) - if diff_output.strip(): - result.append("Mudanças no código:") - result.append(diff_output[:2000]) # Limita o tamanho - result.append("") - except: - pass - - result.append("-" * 80) - result.append("") - - return "\n".join(result) - - except subprocess.CalledProcessError as e: - return f"Erro ao executar comando Git: {e.output}" - except Exception as e: - return f"Erro na análise Git: {str(e)}" - - def _parse_git_log(self, log_output: str) -> list: - """Parse da saída do git log.""" - commits = [] - current_commit = None - - for line in log_output.split('\n'): - if '|' in line and len(line.split('|')) == 5: - # Nova linha de commit - if current_commit: - commits.append(current_commit) - - parts = line.split('|') - current_commit = { - 'hash': parts[0], - 'author': parts[1], - 'email': parts[2], - 'date': parts[3], - 'message': parts[4], - 'files': [] - } - elif line.strip() and current_commit and '\t' in line: - # Linha de estatísticas de arquivo - parts = line.split('\t') - if len(parts) == 3: - additions = parts[0] if parts[0] != '-' else '0' - deletions = parts[1] if parts[1] != '-' else '0' - current_commit['files'].append({ - 'additions': additions, - 'deletions': deletions, - 'file': parts[2] - }) - - if current_commit: - commits.append(current_commit) - - return commits - - -class GitBlameAnalysisTool: - """Ferramenta para analisar autoria de linhas de código usando git blame.""" - - name: str = "Git Blame Analysis Tool" - description: str = ( - "Ferramenta para analisar autoria de linhas de código usando git blame. " - "Útil para entender quem modificou cada parte do código." - ) - - def run(self, repo_path: str, file_path: str) -> str: - """ - Analisa a autoria de um arquivo específico usando git blame. - - Args: - repo_path: Caminho para o repositório Git - file_path: Caminho relativo do arquivo a ser analisado - """ - try: - full_path = os.path.join(repo_path, file_path) - - if not os.path.exists(full_path): - return f"Erro: Arquivo {file_path} não encontrado." - - # Git blame com informações detalhadas - blame_cmd = [ - 'git', '-C', repo_path, 'blame', - '--line-porcelain', - file_path - ] - - blame_output = subprocess.check_output( - blame_cmd, - stderr=subprocess.STDOUT, - text=True - ) - - # Processa e agrupa por autor - authors_stats = self._parse_git_blame(blame_output) - - result = [] - result.append("=" * 80) - result.append(f"ANÁLISE DE AUTORIA: {file_path}") - result.append("=" * 80) - result.append("") - - for author, stats in sorted(authors_stats.items(), - key=lambda x: x[1]['lines'], - reverse=True): - percentage = (stats['lines'] / stats['total_lines']) * 100 - result.append(f"Autor: {author}") - result.append(f"Email: {stats['email']}") - result.append(f"Linhas: {stats['lines']} ({percentage:.1f}%)") - result.append(f"Último commit: {stats['last_commit'][:8]}") - result.append("") - - return "\n".join(result) - - except subprocess.CalledProcessError as e: - return f"Erro ao executar git blame: {e.output}" - except Exception as e: - return f"Erro na análise: {str(e)}" - - def _parse_git_blame(self, blame_output: str) -> dict: - """Parse da saída do git blame.""" - authors = {} - current_commit = None - current_author = None - current_email = None - total_lines = 0 - - for line in blame_output.split('\n'): - if line.startswith('author '): - current_author = line[7:] - elif line.startswith('author-mail '): - current_email = line[12:].strip('<>') - elif line.startswith('summary '): - if current_author: - if current_author not in authors: - authors[current_author] = { - 'email': current_email, - 'lines': 0, - 'last_commit': current_commit, - 'total_lines': 0 - } - authors[current_author]['lines'] += 1 - total_lines += 1 - elif len(line) == 40 and all(c in '0123456789abcdef' for c in line): - current_commit = line - - # Atualiza total de linhas - for author in authors: - authors[author]['total_lines'] = total_lines - - return authors From 1aa05ee3caef2957734744e54ae77788d50abb6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Ren=C3=A3?= Date: Wed, 3 Dec 2025 21:44:23 -0300 Subject: [PATCH 35/36] reportfy --- .github/workflows/main.yml | 34 +++++++ LICENSE | 21 ++++ ReportfyBot.py | 199 +++++++++++++++++++++++++++++++++++++ requirements.txt | 11 +- 4 files changed, 258 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/main.yml create mode 100644 LICENSE create mode 100644 ReportfyBot.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..18af80b --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,34 @@ +name: ReportifyBot Run + +on: + workflow_dispatch: # permite rodar manualmente pelo GitHub + schedule: + - cron: "0 20 * * *" # roda todo dia às 12h UTC (opcional, pode apagar se não quiser agendar) + +jobs: + run-bot: + runs-on: ubuntu-latest + + steps: + - name: Checkout do repositório + uses: actions/checkout@v3 + + - name: Configurar Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + + - name: Instalar dependências + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Rodar bot (relatório + resumo) + env: + MY_API_REPORTFY: ${{ secrets.DISCORD_BOT_TOKEN }} + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + DISCORD_CHANNEL_ID: ${{ secrets.DISCORD_CHANNEL_ID }} + GITHUB_TOKEN: ${{ secrets.REPORTFY_GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ secrets.REPOSITORY }} + + run: python ReportfyBot.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f986cd9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 MatthewNF06 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ReportfyBot.py b/ReportfyBot.py new file mode 100644 index 0000000..683d248 --- /dev/null +++ b/ReportfyBot.py @@ -0,0 +1,199 @@ +import os +import discord +from discord.ext import commands +import asyncio +from unittest.mock import patch +from pathlib import Path +import requests +import re +import base64 +import io + +# Import do Reportify +from reportify import Report + +# === Variáveis de Ambiente === +TOKEN = os.getenv("MY_API_REPORTFY") +CHANNEL_ID = int(os.getenv("DISCORD_CHANNEL_ID")) +GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") + +# Bot +intents = discord.Intents.default() +bot = commands.Bot(command_prefix="!", intents=intents) + +# 📨 Função para enviar mensagens no canal e no privado +async def enviar_status(bot, channel_id, mensagem): + # Mandar no canal do servidor + canal = bot.get_channel(channel_id) + if canal: + await canal.send(mensagem) + + # Mandar no privado para usuários configurados + usuarios_str = os.getenv("DISCORD_TARGET_USERS", "") + if usuarios_str.strip(): + ids = [u.strip() for u in usuarios_str.split(",") if u.strip().isdigit()] + for user_id in ids: + try: + user = await bot.fetch_user(int(user_id)) + await user.send(mensagem) + except Exception as e: + print(f"Erro ao enviar DM para {user_id}: {e}") + +# 📄 Ler último relatório MD +def ler_ultimo_arquivo_md(): + reports_path = Path("./Reports") + if not reports_path.exists() or not reports_path.is_dir(): + return None + + report_dirs = sorted( + [p for p in reports_path.iterdir() if p.is_dir()], + key=lambda p: p.stat().st_mtime, + reverse=True + ) + + if not report_dirs: + return None + + latest_dir = report_dirs[0] + md_files = list(latest_dir.glob("developer_stats_*.md")) + if not md_files: + return None + + contents = [] + for md_file in md_files: + try: + with open(md_file, "r", encoding="utf-8") as f: + contents.append(f"## {md_file.stem}\n\n{f.read()}\n") + except Exception as e: + print(f"Erro ao ler {md_file}: {e}") + + return "\n".join(contents) if contents else None + + + +# Enviar imagens base64 do Relatório +async def mandar_imagens_b64(destino, list_b64): + await destino.send( + "📊 Enviando gráficos do relatório...\n" + "• Gráfico 1: Barras — Prometido vs Entregue\n" + "• Gráfico 2: Linhas — Issues fechadas nos últimos 15 dias" + ) + + for i, img64 in enumerate(list_b64): + try: + img_bytes = base64.b64decode(img64) + buf = io.BytesIO(img_bytes) + + arquivo = discord.File( + fp=buf, + filename=f"grafico_{i+1}.png" + ) + + await destino.send(f"📈 Gráfico {i+1}/{len(list_b64)}:", file=arquivo) + + except Exception as e: + print(f"Erro ao enviar gráfico {i+1}: {e}") + +# 🤖 GEMINI (Uso da Api) +def gerar_resposta_gemini(pergunta): + url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={GEMINI_API_KEY}" + headers = {"Content-Type": "application/json"} + data = { + "contents": [{"parts": [{"text": pergunta}]}] + } + + response = requests.post(url, headers=headers, json=data) + + if response.status_code == 200: + try: + return response.json()['candidates'][0]['content']['parts'][0]['text'] + except Exception: + return "⚠️ Não consegui entender a resposta da IA." + else: + print(response.text) + return f"❌ Erro na API: {response.status_code}" + + + +# 🚀 Fluxo principal +@bot.event +async def on_ready(): + print(f"Bot conectado como {bot.user}") + + await enviar_status(bot, CHANNEL_ID, "🚀 Iniciando geração de relatório...") + + try: + # 1️⃣ Gera relatório + def run_report(): + entradas = ['0', ''] + with patch('builtins.input', side_effect=lambda _: entradas.pop(0) if entradas else ''): + relatorio = Report() + try: + relatorio.run() + except SystemExit: + pass + except Exception as e: + print(f"Erro no Reportify.run(): {e}") + + await asyncio.to_thread(run_report) + await enviar_status(bot, CHANNEL_ID, "📊 Relatório gerado com sucesso!") + + + # 2️⃣ Ler o MD + markdown = ler_ultimo_arquivo_md() + if not markdown: + await enviar_status(bot, CHANNEL_ID, "⚠️ Nenhum relatório encontrado.") + await bot.close() + return + + # ---------- EXTRAIR IMAGENS BASE64 ---------- + regex_base64 = r"data:image/png;base64,([^)\s]+)" + imgs_b64 = re.findall(regex_base64, markdown) + + if imgs_b64: + # Mandar para o canal + canal = bot.get_channel(CHANNEL_ID) + if canal: + await mandar_imagens_b64(canal, imgs_b64) + + # Mandar para os usuários privados + usuarios_str = os.getenv("DISCORD_TARGET_USERS", "") + ids = [u.strip() for u in usuarios_str.split(",") if u.strip().isdigit()] + for user_id in ids: + try: + user = await bot.fetch_user(int(user_id)) + await mandar_imagens_b64(user, imgs_b64) + except Exception as e: + print(f"Erro ao enviar imagens para {user_id}: {e}") + + # 3️⃣ Gerar resumo + prompt = ( + "Você receberá estatísticas individuais de desenvolvedores de um projeto. " + "Para cada desenvolvedor, gere um resumo separado (em Portugues-BR) contendo:\n" + "- Prometido vs. Realizado (se disponível)\n" + "- Throughput (quantas issues fechadas)\n" + "- O nome dentro de uma [] no relatorio, para destacar\n" + "- Quais issues ele abriu ou está responsável\n" + "- Observações sobre atividade, papel no projeto ou padrão de contribuição\n\n" + "Aqui estão os dados:\n\n" + markdown + ) + + await enviar_status(bot, CHANNEL_ID, "📝 Gerando resumo com a IA Gemini...") + resumo = gerar_resposta_gemini(prompt) + + + # 4️⃣ Enviar resumo + for i in range(0, len(resumo), 2000): + await enviar_status(bot, CHANNEL_ID, resumo[i:i+2000]) + + await enviar_status(bot, CHANNEL_ID, "✅ Processo concluído com sucesso!") + + except Exception as e: + await enviar_status(bot, CHANNEL_ID, f"❌ Erro durante execução: {e}") + + finally: + await bot.close() + + +# Executar bot +bot.run(TOKEN) diff --git a/requirements.txt b/requirements.txt index 66d22b4..5af3729 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,4 @@ -crewai==0.201.1 -crewai-tools==0.76.0 -python-dotenv==1.1.1 -PyYAML==6.0.3 -litellm==1.74.9 -qdrant-client==1.15.1 -requests==2.32.3 +discord.py +python-dotenv +requests +reportify-ifes From f0541849327fe2c297f533a3b8236e194c83303d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Ren=C3=A3?= Date: Wed, 3 Dec 2025 22:09:03 -0300 Subject: [PATCH 36/36] Revert "reportfy" This reverts commit 1aa05ee3caef2957734744e54ae77788d50abb6e. --- .github/workflows/main.yml | 34 ------- LICENSE | 21 ---- ReportfyBot.py | 199 ------------------------------------- requirements.txt | 11 +- 4 files changed, 7 insertions(+), 258 deletions(-) delete mode 100644 .github/workflows/main.yml delete mode 100644 LICENSE delete mode 100644 ReportfyBot.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 18af80b..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: ReportifyBot Run - -on: - workflow_dispatch: # permite rodar manualmente pelo GitHub - schedule: - - cron: "0 20 * * *" # roda todo dia às 12h UTC (opcional, pode apagar se não quiser agendar) - -jobs: - run-bot: - runs-on: ubuntu-latest - - steps: - - name: Checkout do repositório - uses: actions/checkout@v3 - - - name: Configurar Python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - - name: Instalar dependências - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - - name: Rodar bot (relatório + resumo) - env: - MY_API_REPORTFY: ${{ secrets.DISCORD_BOT_TOKEN }} - GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} - DISCORD_CHANNEL_ID: ${{ secrets.DISCORD_CHANNEL_ID }} - GITHUB_TOKEN: ${{ secrets.REPORTFY_GITHUB_TOKEN }} - GITHUB_REPOSITORY: ${{ secrets.REPOSITORY }} - - run: python ReportfyBot.py diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f986cd9..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 MatthewNF06 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/ReportfyBot.py b/ReportfyBot.py deleted file mode 100644 index 683d248..0000000 --- a/ReportfyBot.py +++ /dev/null @@ -1,199 +0,0 @@ -import os -import discord -from discord.ext import commands -import asyncio -from unittest.mock import patch -from pathlib import Path -import requests -import re -import base64 -import io - -# Import do Reportify -from reportify import Report - -# === Variáveis de Ambiente === -TOKEN = os.getenv("MY_API_REPORTFY") -CHANNEL_ID = int(os.getenv("DISCORD_CHANNEL_ID")) -GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") - -# Bot -intents = discord.Intents.default() -bot = commands.Bot(command_prefix="!", intents=intents) - -# 📨 Função para enviar mensagens no canal e no privado -async def enviar_status(bot, channel_id, mensagem): - # Mandar no canal do servidor - canal = bot.get_channel(channel_id) - if canal: - await canal.send(mensagem) - - # Mandar no privado para usuários configurados - usuarios_str = os.getenv("DISCORD_TARGET_USERS", "") - if usuarios_str.strip(): - ids = [u.strip() for u in usuarios_str.split(",") if u.strip().isdigit()] - for user_id in ids: - try: - user = await bot.fetch_user(int(user_id)) - await user.send(mensagem) - except Exception as e: - print(f"Erro ao enviar DM para {user_id}: {e}") - -# 📄 Ler último relatório MD -def ler_ultimo_arquivo_md(): - reports_path = Path("./Reports") - if not reports_path.exists() or not reports_path.is_dir(): - return None - - report_dirs = sorted( - [p for p in reports_path.iterdir() if p.is_dir()], - key=lambda p: p.stat().st_mtime, - reverse=True - ) - - if not report_dirs: - return None - - latest_dir = report_dirs[0] - md_files = list(latest_dir.glob("developer_stats_*.md")) - if not md_files: - return None - - contents = [] - for md_file in md_files: - try: - with open(md_file, "r", encoding="utf-8") as f: - contents.append(f"## {md_file.stem}\n\n{f.read()}\n") - except Exception as e: - print(f"Erro ao ler {md_file}: {e}") - - return "\n".join(contents) if contents else None - - - -# Enviar imagens base64 do Relatório -async def mandar_imagens_b64(destino, list_b64): - await destino.send( - "📊 Enviando gráficos do relatório...\n" - "• Gráfico 1: Barras — Prometido vs Entregue\n" - "• Gráfico 2: Linhas — Issues fechadas nos últimos 15 dias" - ) - - for i, img64 in enumerate(list_b64): - try: - img_bytes = base64.b64decode(img64) - buf = io.BytesIO(img_bytes) - - arquivo = discord.File( - fp=buf, - filename=f"grafico_{i+1}.png" - ) - - await destino.send(f"📈 Gráfico {i+1}/{len(list_b64)}:", file=arquivo) - - except Exception as e: - print(f"Erro ao enviar gráfico {i+1}: {e}") - -# 🤖 GEMINI (Uso da Api) -def gerar_resposta_gemini(pergunta): - url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={GEMINI_API_KEY}" - headers = {"Content-Type": "application/json"} - data = { - "contents": [{"parts": [{"text": pergunta}]}] - } - - response = requests.post(url, headers=headers, json=data) - - if response.status_code == 200: - try: - return response.json()['candidates'][0]['content']['parts'][0]['text'] - except Exception: - return "⚠️ Não consegui entender a resposta da IA." - else: - print(response.text) - return f"❌ Erro na API: {response.status_code}" - - - -# 🚀 Fluxo principal -@bot.event -async def on_ready(): - print(f"Bot conectado como {bot.user}") - - await enviar_status(bot, CHANNEL_ID, "🚀 Iniciando geração de relatório...") - - try: - # 1️⃣ Gera relatório - def run_report(): - entradas = ['0', ''] - with patch('builtins.input', side_effect=lambda _: entradas.pop(0) if entradas else ''): - relatorio = Report() - try: - relatorio.run() - except SystemExit: - pass - except Exception as e: - print(f"Erro no Reportify.run(): {e}") - - await asyncio.to_thread(run_report) - await enviar_status(bot, CHANNEL_ID, "📊 Relatório gerado com sucesso!") - - - # 2️⃣ Ler o MD - markdown = ler_ultimo_arquivo_md() - if not markdown: - await enviar_status(bot, CHANNEL_ID, "⚠️ Nenhum relatório encontrado.") - await bot.close() - return - - # ---------- EXTRAIR IMAGENS BASE64 ---------- - regex_base64 = r"data:image/png;base64,([^)\s]+)" - imgs_b64 = re.findall(regex_base64, markdown) - - if imgs_b64: - # Mandar para o canal - canal = bot.get_channel(CHANNEL_ID) - if canal: - await mandar_imagens_b64(canal, imgs_b64) - - # Mandar para os usuários privados - usuarios_str = os.getenv("DISCORD_TARGET_USERS", "") - ids = [u.strip() for u in usuarios_str.split(",") if u.strip().isdigit()] - for user_id in ids: - try: - user = await bot.fetch_user(int(user_id)) - await mandar_imagens_b64(user, imgs_b64) - except Exception as e: - print(f"Erro ao enviar imagens para {user_id}: {e}") - - # 3️⃣ Gerar resumo - prompt = ( - "Você receberá estatísticas individuais de desenvolvedores de um projeto. " - "Para cada desenvolvedor, gere um resumo separado (em Portugues-BR) contendo:\n" - "- Prometido vs. Realizado (se disponível)\n" - "- Throughput (quantas issues fechadas)\n" - "- O nome dentro de uma [] no relatorio, para destacar\n" - "- Quais issues ele abriu ou está responsável\n" - "- Observações sobre atividade, papel no projeto ou padrão de contribuição\n\n" - "Aqui estão os dados:\n\n" + markdown - ) - - await enviar_status(bot, CHANNEL_ID, "📝 Gerando resumo com a IA Gemini...") - resumo = gerar_resposta_gemini(prompt) - - - # 4️⃣ Enviar resumo - for i in range(0, len(resumo), 2000): - await enviar_status(bot, CHANNEL_ID, resumo[i:i+2000]) - - await enviar_status(bot, CHANNEL_ID, "✅ Processo concluído com sucesso!") - - except Exception as e: - await enviar_status(bot, CHANNEL_ID, f"❌ Erro durante execução: {e}") - - finally: - await bot.close() - - -# Executar bot -bot.run(TOKEN) diff --git a/requirements.txt b/requirements.txt index 5af3729..66d22b4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,7 @@ -discord.py -python-dotenv -requests -reportify-ifes +crewai==0.201.1 +crewai-tools==0.76.0 +python-dotenv==1.1.1 +PyYAML==6.0.3 +litellm==1.74.9 +qdrant-client==1.15.1 +requests==2.32.3