Prompt Engineering para código: como escrever instruções que a IA entende
No primeiro artigo dessa série contei como o AGENTS.md de 773 linhas foi o manual que ensinou a IA a pensar. Agora vou abrir a caixa-preta dos prompts: como eu estruturava as instruções, o que funcionava, o que não funcionava e por que "ser específico, dar exemplos e definir o que NÃO fazer" não é frase de efeito — é a diferença entre uma IA que entrega e uma IA que alucina.
Se você quiser pular a história e ir direto para a parte útil, vá para a seção: O que funcionava e o que não funcionava.
O começo: prompts vagos geram código vago
Quando comecei o ProbLab no opencode, meu primeiro impulso foi pedir as coisas de forma genérica. Eu vinha de uma experiência ruim em 2025, onde a IA havia deletado uma regra de negócio inteira só para o build parar de falhar. Dessa vez eu queria fazer direito.
O problema é saber o que pedir no prompt. Vejo muita produção de conteúdo nas redes sociais toda de forma genérica — até nos anúncios patrocinados de IA, que mostravam que bastava uma instrução simples e pronto: a "IA iria pensar em tudo que você não pensou". Mas a realidade do vibe coding não é bem assim.
Apareceram várias notícias a respeito de quedas de aplicação, vazamentos de dados e vulnerabilidades exploradas — tudo em aplicações feitas por IA na tendência do vibe coding, que foram para produção sem o cuidado necessário.
Aqui vem um ponto importante para quem tem experiência em desenvolvimento de software: saber o que uma aplicação precisa ter para não apenas funcionar como o esperado, mas também para não deixar que outros desenvolvedores quebrem o funcionamento dela — com a aplicação adequada de testes — e, principalmente, para não abrir brechas de segurança, algo essencial nos dias de hoje.
Especificidade no prompt (desde o primeiro)
O melhor caminho que tomei foi gastar um bom tempo em um arquivo chamado IDEA.md, onde comecei a concentrar ali tudo sobre o projeto: objetivo, tecnologias, linguagem, estrutura, banco de dados e muito mais. Realmente escrevi muitos detalhes dentro desse arquivo, pois ele seria o primeiro prompt que eu iria enviar à IA para ela criar o projeto.
Quando se tem experiência com aplicações — principalmente backend —, você vai inserindo no prompt o máximo de detalhes possível que não podem faltar no projeto, até mesmo o padrão de banco de dados que você gosta de usar, a forma como se comunicar com o banco, filas em locais estratégicos, etc.
Um exemplo prático é a quantidade de detalhes que um endpoint precisa ter para funcionar com as melhores práticas de desenvolvimento possíveis. Para um endpoint, você precisa especificar no prompt:
- Implementar o endpoint de autenticação de acordo com o padrão async-first do AGENTS.md.
- Usar AsyncSession e select() do SQLAlchemy 2.0 (nunca session.query())
- JWT RSA (RS256) com refresh token rotativo e blacklist de revogados
- Senha com bcrypt
- Rate limiting por IP (SlowAPI) conforme rate limit
- Testes de segurança equivalentes
- Validação Pydantic em todos os inputs
- Não commitar sem minha autorização
A IA entregou um endpoint muito melhor. Por quê? Porque eu não dei apenas a instrução, eu dei o "mapa": o padrão (async-first), a regra (select() em vez de session.query()), o mecanismo (JWT RSA, bcrypt), a proteção (rate limiting), o teste (test_security.py) e a fronteira (não commitar sem autorização).
As regras na pasta .agents: aprendizado que persiste
Percebi rapidamente que repetir tudo no prompt a cada sessão era inviável. Era cansativo, propenso a esquecimento e, principalmente, não acumulava conhecimento. Foi então que organizei as regras e agentes dentro da pasta .agents — o "cérebro" do projeto.
Estrutura real do .agents do projeto:
.agents/
├── agent/
│ ├── security-auditor.md # Auditor de cibersegurança (eu usava sempre)
│ └── doc-write.md # Escritor de documentação técnica
├── skills/
│ ├── plan/SKILL.md # Habilidade de planejamento de projetos
│ └── senior-dev/SKILL.md # Habilidade de desenvolvimento sênior
├── rules/
│ ├── modelagem-dados-postgresql.md # Padrões obrigatórios de domínios/auditoria
│ ├── plan-structure.md # Estrutura de arquivos de plano
│ └── plan-quality.md # Qualidade de planejamento
└── workflow/
└── atomic-commit/WORKFLOW.md # Protocolo de commit atômico
Cada arquivo ali era, na verdade, um prompt cuidadosamente construído que eu não precisava redigir de novo. O sistema de habilidades (skills), regras (rules) e agentes (agents) permitia que eu invocasse comportamentos especializados com comandos curtos.
O agente security-auditor.md: o guardião recorrente
Um dos agentes que eu usava periodicamente era o security-auditor.md. Ele não era um prompt solto — era um subagente completo, com temperature: 0.0 (zero criatividade, máxima precisão), mode: subagent e permissões explícitas:
---
description: Auditor de Cyber Segurança especializado em análise estática...
mode: subagent
temperature: 0.0
color: red
permission:
read: allow
write: allow
grep: allow
glob: allow
question: allow
task: allow
---
O que tornava esse agente especial era o pensamento de ameaças. Ele não audita passivamente; ele pensa como um atacante. O próprio arquivo definia os vetores que ele deveria caçar a cada revisão:
- IDOR (Insecure Direct Object Reference)
- Race Condition (em transações financeiras, ex.: ganho infinito via reembolso)
- SQL Injection, XSS, SSRF, CSRF
- Enumeração de usuários
- Token/Segment Leakage
- Ausência de rate limiting
- Exposição de dados sensíveis em logs ou erros
Eu invocava o auditor com comandos diretos no opencode. Quando eu queria uma verificação completa do módulo recém-criado, usava:
> @security-auditor Auditar o módulo de checkout e cart_service.py.
> Foco em: race condition, validação de propriedade de itens e exposição de segredos.
E quando eu queria forçar uma análise detalhada em todo o projeto bastava um comando:
> @security-auditor analise todo o projeto para encontrar as falhas de segurança.
O agente age como um analista ou como um hacker (conforme instrução no comando). Isso forçava a análise sob uma perspectiva que eu, como não-programador Python, não tinha naturalmente. Ele analisava o código, identificava a vulnerabilidade, mostrava em tela o que devia corrigir, apresentava como poderia ser corrigido, explicava por que a nova versão era mais segura, aplicava a correção e escrevia um teste que tentava explorar a falha. Bastava eu autorizar a correção.
Eu preferir ver as sugestões de correção da IA primeiro antes de aplicá-las, fiz isso para ver se a solução realmente era plausível para a vulnerabilidade em questão - onde algumas delas já conhecia - pois em alguns momentos a IA sugerir soluções nada eficaz como a questão do controle de token que existia endpoint fora da cobertura de autenticação e a IA sugeriu adicionar que a rota continuasse sem validar o token.
O resultado dessas auditorias está nos commits do projeto:
fix(security): auditoria - blacklist persistente, rate limit, timeout, CSP
fix(security): corrigir 6 vulnerabilidades críticas para produção
feat(security): implementar 4 camadas de segurança no painel admin
test(security): adicionar 18 testes para as 4 camadas de segurança admin
O mais impressionante para mim: essas vulnerabilidades críticas foram encontradas e corrigidas por uma IA dentro do meu projeto, usando um agente que eu mesmo escrevi. Eu não teria ideia de onde olhar. O agente sabia — porque o prompt o ensinou a olhar.
As regras: modelagem de dados PostgreSQL
Além dos agentes, as rules garantiam que toda geração seguisse padrões obrigatórios, sem eu precisar lembrar a IA. A regra modelagem-dados-postgresql.md era um exemplo perfeito. Ela dizia, sem rodeios:
## Diretrizes Obrigatórias de Implementação
### 1. Uso de Domínios (Domains)
- **PROIBIDO**: Utilizar tipos primitivos diretamente para campos padronizados
- **OBRIGATÓRIO**: Utilizar sempre os domínios previamente definidos...
- Exemplo correto: `criado_em public.dm_criado_em NOT NULL`
- Exemplo proibido: `criado_em TIMESTAMPTZ NOT NULL`
Perceba a estrutura — é exatamente o que aprendi a fazer nos prompts:
- Regra explícita (o padrão)
- O que é PROIBIDO (a negativa, para a IA não errar)
- O que é OBRIGATÓRIO (a positiva)
- Exemplo correto (o que seguir)
- Exemplo proibido (o que evitar)
Essa estrutura "exemplo certo + exemplo errado" foi o formato mais eficaz que encontrei. A IA entende melhor uma regra quando vê os dois lados da moeda.
Exemplos de comandos no opencode
Como todo o desenvolvimento, pelo menos foi a maior parte, acontecia no terminal do opencode, os comandos eram o meu vocabulário. Vou compartilhar alguns que usei ao longo dos meses, organizados por tipo de intenção.
Implementação de uma funcionalidade
> Seguir o padrão do módulo simulador no AGENTS.md.
> - Geração assíncrona em lotes via worker (registro na tabela pl_processamento_controle)
> - Verificação de raridade e histórico
> - Combinações ordenadas e sem repetições
> - Use com for_update() onde houver leitura antes de escrita
> - Gerar testes de integração (pytest) para o novo serviço
Revisão e correção de um bug
> Corrigir o erro "Cannot read properties of null (reading 'save')" no dashboard admin.
> Causa provável: Chart.js dentro de <template x-if> do Alpine.js.
> Aplicar a regra obrigatória do AGENTS.md: animation: false nos charts.
> Canvas deve ficar dentro de <template x-if="!loading && data">.
> Criar chart com try/catch e verificar canvas.isConnected antes de instanciar.
Auditoria de segurança usando o agente
> @security-auditor Auditoria completa do fluxo de carrinho e pagamento.
> Atenção a: race condition em pedidos (carrinho → pedido), IDOR em itens,
> vazamento de segredos em logs e validação de propriedade de análises.
Um detalhe que vale registrar: mesmo quando um comando deles vem com um typo ou gíria, um prompt bem contextualizado sobrevive — o agente entende a intenção pelo contexto. Especificidade e contexto são a base.
Commit atômico via workflow
> /commit
Esse simples comando acionava o workflow atomic-commit, que perguntava sobre tickets, analisava as mudanças com git status e git diff --cached, determinava o tipo (feat, fix, docs, etc.), gerava a mensagem seguindo Conventional Commits e pedia confirmação com [C]onfirmar [E]ditar [A]bortar. Nunca usava git add . (PROIBIDO na regra) — adicionava arquivos individualmente.
Geração de documentação
> @doc-write type=development mode=create output=docs/DEVELOPMENT.md
O agente doc-write explorava o código real (nunca inventava caminhos — regra crítica), coletava fatos e escrevia o documento com o template correto e o marcador <!-- generated-by: doc-write -->.
O que funcionava e o que não funcionava
Depois de 4 meses e 230 commits, consigo separar com clareza o que valia a pena e o que era perda de tempo.
O que não funcionava
- Prompts vagos — pedir "crie um módulo" sem contexto gerava código genérico e inseguro. Custava mais tempo de revisão do que ter escrito um bom prompt.
- Uma instrução gigante para todas as tarefas — dar um prompt enorme cobrindo o projeto inteiro diluía o foco. A IA se perdia no meio e aplicava as regras de forma inconsistente.
- Confiar em suposições — pedir um padrão e não citar a regra no AGENTS.md fazia a IA "esquecer" as convenções na prática. A IA precisa que você a aponte para o contexto certo.
- Ignorar as negativas — quando eu só dizia "use x", a IA frequentemente fazia "x" E "y" que eu não queria. Definir o que NÃO fazer reduziu drasticamente esses desvios.
O que funcionava
-
Especificidade com referência ao AGENTS.md — apontar exatamente qual seção/seguir do manual transformava um pedido genérico em uma tarefa bem-delimitada.
-
Exemplos corretos + exemplos proibidos — o formato mais eficaz. A IA entende o padrão vendo os dois lados.
-
Regras persistentes em .agents — as
ruleseagentstransformavam meu conhecimento em comportamento automatizado. Osecurity-auditor.mdera o maior exemplo: eu não precisava dizer à IA o que procurar a cada auditoria — o agente já sabia. -
Gatilhos curtos com intenção clara — comandos como
/commit,Auto-Ataquee@security-auditoracionavam comportamentos completos e testados. Menos digitação, mais consistência. -
Delegar verificação a um agente especializado — auditar segurança, escrever docs ou revisar código com agentes especializados rendia resultados melhores do que pedir a mesma IA "geral" para fazer tudo.
O padrão que funciona: exemplo + negativa + fronteira
Se eu tivesse que destilar tudo em um único padrão de prompt, seria assim:
[Cenário: o que/quem — arquivo, módulo, agente]
[Regra: o padrão a seguir — cite a seção do AGENTS.md ou rule]
[Exemplo correto: o que esperar]
[PROIBIDO: o que NÃO fazer]
[Teste: como validar — qual teste rodar]
[Fronteira: onde parar — ex.: não commitar]
Quando eu seguia essa estrutura, a IA QUASE sempre acertava de primeira ou com ajustes mínimos. De vez em quando apareciam surpresas desagradáveis.
Conclusão
O prompt engineering aqui não foi sobre "dizer coisas bonitas para a IA". Foi sobre transferir intenção e conhecimento com precisão. Quanto mais eu conseguia empacotar meu conhecimento de domínio (arquitetura, segurança, padrões) em instruções claras + regras persistentes, mais autonomia a IA podia ter com qualidade.
O security-auditor.md é o caso que mais me marca. Um agente que eu escrevi, com temperature: 0.0, me ajudou a encontrar e corrigir vulnerabilidades críticas num projeto em Python — uma linguagem que eu não domino. Não foi mágica. Foi um prompt bem construído que ensinou a IA a pensar como um auditor de segurança.
E é exatamente esse o ponto da série: não é sobre a IA substituir o programador, é sobre o programador aprender a instruir a IA com a clareza que ela exige.
