Claude Code em monorepo com várias linguagens: ele se perde?

Claude Code em monorepo com várias linguagens no mesmo repositório
Resposta rápida

Claude Code em monorepo não se perde por mágica nem acerta por sorte: o que decide é qual contexto está carregado na hora da edição. A documentação oficial tem uma página dedicada a monorepos e bases grandes, e ela aponta quatro frentes: CLAUDE.md aninhados, sparse worktrees, code intelligence e skills por pacote. Na prática isso vira um CLAUDE.md curto na raiz (ele carrega no início de toda sessão), um CLAUDE.md por subpasta (carrega sob demanda, na primeira leitura), regras em .claude/rules/ escopadas por paths, plugin de LSP por linguagem e subagente por área. Bora configurar?

Poucas coisas incomodam mais que ver o agente aplicar a convenção de uma linguagem dentro de outra pasta

O cenário é conhecido: front numa pasta, back em outra, um monte de script solto num canto, e cada uma dessas partes com regra própria de estilo, de teste e de nomenclatura

Aí você pede um ajuste no back e o agente resolve "organizar" o import do jeito que o front faz, porque foi isso que ele leu no começo da sessão

Este post é um passo a passo de configuração, montado em cima do que a documentação oficial descreve

Vamos da peça mais básica (CLAUDE.md por pasta) até o hook de formatação, com o erro comum de cada passo 🙂

O que você precisa antes de configurar

Lista curta, dá pra conferir tudo em dois minutos:

  • Um repositório com as pastas de fato separadas por linguagem ou por pacote (front, back, scripts, o que for)
  • Claude Code instalado e rodando dentro desse repositório
  • Acesso ao /init e ao /plugin dentro da sessão
  • Permissão pra editar os arquivos de configuração do projeto (é lá que moram settings, rules e skills)
  • Os binários de language server já instalados na sua máquina

Esse último item pega gente desprevenida

O plugin de code intelligence configura a conexão, mas o binário do language server é instalado separadamente: gopls pra Go, pyright-langserver pra Python, rust-analyzer pra Rust, typescript-language-server pra TypeScript

O plugin NÃO instala por você

Como orientar o Claude Code sobre qual parte do repositório ele está mexendo

A ideia central: em vez de um arquivo gigante tentando descrever o repositório inteiro, você deixa cada pedaço de instrução perto do código que ele governa

A página oficial sobre bases grandes e monorepos recomenda frentes como CLAUDE.md aninhados, sparse worktrees e code intelligence pra manter o agente focado no código em que você está mexendo

Bora ver os passos na prática?

  1. Rode /init na raiz

Dentro da sessão do Claude Code, o /init gera um CLAUDE.md inicial: ele lê a estrutura do projeto e escreve comandos de build, arquitetura e convenções

/init

O erro comum deste passo: tratar a saída do /init como verdade final e sair usando. Ele é um ponto de partida, você ainda vai revisar o que ficou lá dentro

  1. Deixe o CLAUDE.md da raiz curto

O arquivo da raiz é lido no início de TODA sessão

E os arquivos CLAUDE.md são carregados por inteiro, sem truncamento por tamanho, só que arquivos mais curtos produzem melhor aderência às instruções

Ou seja: enfiar a convenção do front, a do back e a dos scripts tudo no mesmo arquivo de raiz é justamente o que faz o agente misturar as coisas

Na raiz fica o que vale pro repositório inteiro: como buildar, o desenho geral, onde cada coisa vive

O erro comum deste passo: usar o CLAUDE.md da raiz como despejo de tudo que você já pediu ao agente alguma vez

  1. Crie um CLAUDE.md por subpasta

Aqui entram as convenções locais, aquelas que só fazem sentido dentro daquele pacote

CLAUDE.md
apps/web/CLAUDE.md
services/api/CLAUDE.md
scripts/CLAUDE.md

O detalhe que muda tudo: o CLAUDE.md de subdiretório tem carregamento preguiçoso

Ele entra no contexto sob demanda, só quando o Claude lê um arquivo daquela pasta com a ferramenta Read

O erro comum deste passo: escrever a regra na subpasta e esperar que ela valha desde o "oi" da sessão. Enquanto nenhum arquivo daquela pasta foi lido, aquela instrução simplesmente não está no contexto

  1. Quebre as instruções em .claude/rules/

Dá pra organizar instruções em vários arquivos markdown dentro do diretório .claude/rules/ do projeto, um arquivo por tema, com nomes descritivos

.claude/rules/testing.md
.claude/rules/api-design.md

É o mesmo raciocínio de módulo que tu já usa no código: um assunto por arquivo, nome que diz o que tem dentro

Esse formato também ajuda muito na hora de padronizar o agente entre vários devs do mesmo repositório, porque a regra vira arquivo versionado em vez de combinado verbal

O erro comum deste passo: criar um rules.md único e voltar pro problema do arquivo gigante, só que em outra pasta 😛

  1. Escope a regra por caminho com paths

Essa é a peça que resolve o "padrão de uma linguagem vazando na outra"

Regras podem ser escopadas por caminho usando o campo paths no frontmatter YAML: elas só carregam quando o Claude trabalha em arquivos que batem com os globs informados, o que economiza contexto

---
paths:
  - "src/api/**"
---

Convenções de design da API deste serviço...

Dá pra escopar por pasta (tudo que estiver sob src/api/) ou por extensão, tipo os arquivos terminados em .test.ts e .test.tsx recebendo só a regra de teste

O erro comum deste passo: glob largo demais. Se o padrão pega o repositório inteiro, tu escreveu um arquivo de raiz com passos extras

  1. Instale o plugin de LSP da linguagem

Plugins de code intelligence configuram conexões via Language Server Protocol, a mesma tecnologia por trás do code intelligence do VS Code

Com isso o agente pula pra definição, encontra referências, vê tipo no hover e rastreia hierarquia de chamadas, em vez de sair varrendo a árvore de arquivos

Ele também passa a receber diagnóstico automático depois das edições

O marketplace oficial tem plugin de LSP pra C/C++, C#, Go, Java, Kotlin, Lua, PHP, Python, Rust, Swift e TypeScript

Pra achar, é /plugin, aba Discover, busca por lsp

Ou direto pelo nome:

/plugin install typescript-lsp@claude-plugins-official

O erro comum deste passo: instalar o plugin e parar por aí, esquecendo do binário do language server na máquina

  1. Liste em worktree.sparsePaths só o que a sessão toca

A configuração worktree.sparsePaths usa git sparse-checkout pra escrever em disco apenas os diretórios listados, mais os arquivos de nível raiz (package.json, tsconfig.base.json, lock files)

O ganho é direto: worktree mais rápida e menor, porque o pacote que ninguém vai tocar nem desce

Agora o detalhe que derruba muita gente: diretório de raiz NÃO entra automaticamente

Ou seja, se você quer o settings.json, as rules e as skills da raiz do repositório disponíveis lá dentro, .claude precisa estar listado explicitamente

E tem mais: todas as worktrees de uma mesma sessão compartilham o mesmo sparsePaths

Se um subagente precisa de um pacote e outro precisa de outro, os DOIS caminhos têm que estar listados

O erro comum deste passo: montar a lista pensando numa tarefa só, e descobrir no meio do caminho que o outro pacote não está no disco

  1. Crie um subagente por área

O contexto de um subagente começa do zero, sem a conversa do agente principal, e é dimensionado pelo modelo do próprio subagente

Se liga no que isso significa num repositório multi linguagem: o subagente que mexe no back não carrega o entulho da discussão sobre o front

Ele roda o próprio system prompt, a auto memory da conversa principal não é carregada nele, e as leituras grandes ficam no contexto dele em vez de inchar o principal

Subagentes personalizados são configurados por frontmatter, com campos de identificação, acionamento, ferramentas e modelo: name, description, tools e model

---
name: api-service
description: Ajustes nos serviços da pasta services/
tools: Read, Edit, Bash
---

Você trabalha apenas nos serviços de backend...

O campo model existe também, caso você queira fixar o modelo daquele subagente

O erro comum deste passo: dar acesso a todas as ferramentas pra um subagente que só deveria ler e ajustar uma pasta. Dá pra limitar as ferramentas, use isso

  1. Amarre o formatador certo com um hook

Um hook PostToolUse com o matcher Edit|Write dispara apenas depois das ferramentas de edição de arquivo

Sem matcher, ele dispara em toda ocorrência do evento

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "scripts/format-arquivo.sh" }
        ]
      }
    ]
  }
}

O exemplo oficial de hook extrai o caminho do arquivo com jq e repassa pro formatador

É nesse script que mora a decisão "esse arquivo é Go, então roda o formatador de Go", tirando do modelo a responsabilidade de lembrar disso

O erro comum deste passo: deixar o hook sem matcher e ele acabar disparando em situação que não tem arquivo nenhum pra formatar

Quando o agente aplica o padrão da linguagem errada: sintomas e correções

Agora a parte prática de verdade: o que acontece quando algo não pega

A regra foi editada e o agente continua ignorando

Sintoma: você corrigiu o CLAUDE.md da subpasta ou o arquivo de regra, mas o comportamento antigo continua

Causa: CLAUDE.md aninhados e regras com frontmatter paths não são reinjetados automaticamente no contexto

Solução: eles recarregam na próxima vez que o Claude lê um arquivo daquela subpasta, ou um arquivo que casa com o padrão da regra

Como prevenir: depois de editar uma regra escopada, encaminhe o trabalho pra um arquivo que bate com aquele escopo antes de esperar o novo comportamento

A convenção da subpasta simplesmente não apareceu

Sintoma: o agente aplicou o padrão geral do repositório dentro de um pacote que tem regra própria

Causa: o CLAUDE.md daquela pasta carrega sob demanda, na primeira leitura de um arquivo dali. Se ele ainda não leu nada daquela pasta, a convenção local não está no contexto

Solução: faça o agente ler um arquivo daquela pasta antes de pedir a edição

Como prevenir: duplique no arquivo de regra escopado por paths aquilo que for realmente crítico, já que a regra por caminho carrega quando ele trabalha em arquivos que casam com o glob

O plugin de LSP está instalado mas não navega

Sintoma: nada de pular pra definição nem de encontrar referências, o agente volta a varrer arquivos

Causa: o binário do language server não está instalado

Solução: instale o binário da linguagem (gopls, pyright-langserver, rust-analyzer, typescript-language-server)

Como prevenir: trate o binário como dependência de setup do repositório, na mesma lista onde já estão as outras ferramentas de linha de comando

A worktree não tem as regras da raiz

Sintoma: dentro da worktree o agente age como se as regras do projeto não existissem

Causa: a configuração worktree.sparsePaths escreve em disco apenas os diretórios listados mais os arquivos de nível raiz. Diretórios de raiz não entram automaticamente

Solução: liste .claude explicitamente em worktree.sparsePaths pra ter o settings.json, as rules e as skills da raiz disponíveis lá dentro

Como prevenir: sempre que criar essa configuração, .claude é o primeiro item da lista

O subagente não alcança o pacote de que precisa

Sintoma: um subagente trabalha bem e o outro reclama que o código não está lá

Causa: todas as worktrees de uma mesma sessão compartilham o mesmo sparsePaths

Solução: se um subagente precisa de um pacote e outro precisa de outro, os DOIS caminhos têm que estar listados

Como prevenir: monte a lista pensando na sessão inteira, não em um subagente por vez

O diretório extra enxerga os arquivos mas não traz as configurações

Sintoma: o agente lê o código do outro diretório, só que as skills, comandos e subagentes de lá não aparecem

Causa: os três caminhos de dar acesso a diretórios além do diretório de trabalho não são equivalentes

Caminho Acesso aos arquivos Carrega skills, comandos e subagentes daquele diretório
--add-dir <path> (argumento de CLI na inicialização) sim sim
/add-dir (durante a sessão) sim sim
permissions.additionalDirectories (arquivo de settings) sim não

Solução: se você quer a configuração daquele diretório junto, use --add-dir na inicialização ou /add-dir na sessão

Como prevenir: guarde a diferença pelo propósito. O campo de settings é acesso a arquivo, os comandos carregam a configuração

Vale lembrar que arquivos em diretórios adicionais seguem as mesmas regras de permissão do diretório de trabalho original: ficam legíveis sem prompt e a permissão de edição acompanha o modo de permissão vigente

Se o teu caso é ter dois repositórios abertos ao mesmo tempo, esse assunto de trabalhar em dois projetos ao mesmo tempo sem misturar contexto conversa direto com isso aqui

Como isso fica em um repositório com front, back e scripts

Bora traduzir tudo pro arranjo real, aquele com três mundos convivendo

Regras escopadas por glob de cada pasta. Estilo e teste do front num arquivo com paths apontando pra pasta do front, o mesmo pro back, o mesmo pros scripts. A regra de teste pode ir mais longe e mirar direto os arquivos terminados em .test.ts e .test.tsx, em vez da pasta inteira

Um subagente por área, com ferramentas restritas. Como o contexto dele começa limpo e ele roda o próprio system prompt, o subagente do back não carrega nada do que foi discutido sobre o front. E dá pra limitar as ferramentas dele pelo frontmatter

LSP por linguagem que existe de fato no repositório. Se tem TypeScript e Python convivendo, são dois plugins e dois binários. A navegação por definição e referência é o que evita o agente sair varrendo pasta que não tem nada a ver com a tarefa

sparsePaths listando o que a sessão realmente toca. Menos diretório escrito em disco deixa a worktree mais rápida e menor, e o .claude entra na lista pra as regras da raiz irem junto

--add-dir quando o código vive fora do diretório de trabalho. Aquele pacote compartilhado que mora em outro repo, por exemplo

Um hook de formatação por tipo de arquivo. Assim a decisão de qual formatador roda sai do modelo e vira script determinístico

Repara no padrão: nenhuma dessas peças tenta fazer o agente "lembrar" de tudo o tempo todo

Todas elas fazem a mesma coisa, que é colocar a instrução certa perto do arquivo certo

Conclusão

A pergunta do título tem uma resposta meio chata: o agente se perde na medida em que o contexto dele está errado na hora da edição

Um CLAUDE.md gigante na raiz é exatamente a receita pra isso, porque ele carrega tudo em toda sessão e mistura convenção de pasta que não tem relação nenhuma uma com a outra

O caminho oposto é contexto certo na hora certa: raiz enxuta, subpasta com o que é local, .claude/rules/ com um assunto por arquivo e paths decidindo quando cada coisa entra

Próximo passo prático, e dá pra fazer hoje:

  1. Rode /init na raiz
  2. Corte o CLAUDE.md da raiz pro essencial (build, arquitetura, o que vale pro repositório inteiro)
  3. Mova o resto pra .claude/rules/ com paths, uma pasta por vez

Uma pasta por vez mesmo, sem querer refatorar o repositório inteiro num sábado à tarde haha

Depois disso, o plugin de LSP e os subagentes por área entram como camada extra…

até o próximo post! =)

Perguntas frequentes

Como evitar que o Claude Code aplique a convenção do front dentro do back num monorepo?

Separando as instruções por pasta em vez de um CLAUDE.md único: a raiz fica com o que vale pro repositório inteiro, e cada subpasta (apps/web, services/api) recebe seu próprio CLAUDE.md com as convenções locais. Some a isso regras com o campo paths no frontmatter, escopadas por glob, e o padrão de uma linguagem para de vazar pra outra.

O CLAUDE.md de uma subpasta já carrega no início da sessão, igual o da raiz?

Não. O CLAUDE.md da raiz é lido no início de toda sessão, mas o de subdiretório tem carregamento preguiçoso: só entra no contexto quando o Claude lê um arquivo daquela pasta com a ferramenta Read. Enquanto isso não acontece, a instrução da subpasta simplesmente não está disponível.

As regras escopadas por paths ficam no contexto pra sempre depois de carregar uma vez?

Não, elas não são reinjetadas automaticamente. CLAUDE.md aninhados e regras com paths no frontmatter recarregam na próxima vez que o Claude lê um arquivo daquela subpasta ou um arquivo que bate com o padrão da regra.

Instalar o plugin de LSP já é suficiente pra ter code intelligence funcionando no monorepo?

Não, falta uma peça. O plugin configura a conexão via Language Server Protocol, mas o binário do language server precisa ser instalado à parte na máquina, por exemplo gopls pra Go, pyright-langserver pra Python, rust-analyzer pra Rust ou typescript-language-server pra TypeScript.

Sparse worktrees já trazem as regras e skills da raiz automaticamente?

Não. A configuração worktree.sparsePaths escreve em disco os diretórios listados mais os arquivos de nível raiz, como package.json e lock files. Diretórios de raiz não entram automaticamente, então o .claude precisa ser listado explicitamente pra levar settings.json, rules e skills pra dentro da worktree.

Um subagente por área do monorepo realmente evita misturar contexto entre front e back?

Sim, porque o contexto de um subagente começa do zero, sem a conversa do agente principal, e é dimensionado pelo modelo dele próprio. Ele roda com system prompt próprio, sem a auto memory da conversa principal, então o que foi discutido sobre o front não entra no contexto de quem mexe no back.



Escrito por | Matheus Battisti

Matheus Battisti
Fundador da Hora de Codar

Programador apaixonado pelo mundo das tecnologias, sempre buscando em aprender e se aprofundar em linguagens, frameworks e o que mais for necessário para executar um bom trabalho. Agora tem uma nova missão que é de passar seu conhecimento adiante para formar novos programadores e especializar mais os que já são.

Subscribe
Notify of
guest

0 Comentários
Oldest
Newest Most Voted

Formações

Formação Vibe Coding

Formação Vibe Coding

Do Prompt ao Produto: Crie Software Real com IA

  • 474 aulas
  • 20 projetos
  • 39h 30min

Blog | Mais populares