Pular para o conteúdo principal

Prompt Engineering para código: como escrever instruções que a IA entende

· 11 min para ler
Bruno Nogueira
Desenvolvedor de Software

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:

  1. Regra explícita (o padrão)
  2. O que é PROIBIDO (a negativa, para a IA não errar)
  3. O que é OBRIGATÓRIO (a positiva)
  4. Exemplo correto (o que seguir)
  5. 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​

  1. 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.
  2. 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.
  3. 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.
  4. 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​

  1. 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.

  2. Exemplos corretos + exemplos proibidos — o formato mais eficaz. A IA entende o padrão vendo os dois lados.

  3. Regras persistentes em .agents — as rules e agents transformavam meu conhecimento em comportamento automatizado. O security-auditor.md era o maior exemplo: eu não precisava dizer à IA o que procurar a cada auditoria — o agente já sabia.

  4. Gatilhos curtos com intenção clara — comandos como /commit, Auto-Ataque e @security-auditor acionavam comportamentos completos e testados. Menos digitação, mais consistência.

  5. 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.