semcode
Sobre o MCP de busca semântica de código (semcode). Indexa símbolos de código e histórico de commits. Combina embeddings densos com vetores esparsos BM25 para busca híbrida que equilibra compreensão semântica com precisão de palavras-chave.
Documentação
semcode
Um servidor MCP (Model Context Protocol) que fornece busca semântica híbrida sobre código em um conjunto de
repositórios GitHub que você lista em config.yaml. Ele analisa símbolos
com Tree-sitter e indexa tanto o código quanto o histórico de commits do git, para que clientes de IA possam consultá-los por
linguagem natural ou por nome de símbolo.
A recuperação híbrida combina embeddings densos com BM25, de modo que tanto consultas em linguagem natural
("onde publicamos eventos de pedidos?") quanto buscas por nome de símbolo (PlaceOrderRequest) funcionam bem.
Submetido em:
Como funciona
- Busca arquivos de código-fonte dos repositórios GitHub configurados
- Analisa símbolos de código (funções, classes, métodos, componentes) usando Tree-sitter
- Gera dois embeddings por símbolo — um vetor semântico denso (provedor plugável: Jina Code V2 por padrão, ou Voyage / OpenAI / Ollama) e um vetor esparso BM25 baseado em tokens de identificadores de código (camelCase / snake_case divididos em subpalavras)
- Armazena ambos no Qdrant e os recupera com busca híbrida — Fusão de Classificação Recíproca (RRF) sobre os resultados densos e esparsos — para que consultas em linguagem natural e buscas por nome de símbolo funcionem bem
- Opcionalmente, indexa o histórico de commits em uma coleção Qdrant separada (somente denso)
- Expõe ferramentas de busca e indexação por meio do protocolo MCP (e uma pequena API HTTP)
A indexação é incremental — arquivos são ignorados quando o SHA do blob Git corresponde à última versão indexada.
Arquivos que não existem mais (ou que analisam para zero símbolos) são limpos automaticamente. Passe force: true
para re-incorporar tudo.
Quer mais detalhes? Confira o blog!
Documentação
Documentação aprofundada dos internals do sistema RAG está disponível em goodbyeplanet.github.io/semcode:
- Pipeline de ingestão — como o código é descoberto, analisado, incorporado e armazenado
- Vetores densos — provedores de embedding e estratégia de texto
- Vetores esparsos — BM25 e o tokenizador de código
- Recuperação com RRF — busca híbrida e ferramentas MCP
- Configuração — todas as variáveis de ambiente e config.yaml
- Changelog — histórico de versões, gerado a partir de mensagens de commit
Linguagens suportadas
A linguagem é detectada automaticamente pela extensão do arquivo ou nome do arquivo — nenhuma configuração necessária.
Go, Java, Python, TypeScript / JavaScript (React), Rust, C#, C, C++, Ruby, PHP, Kotlin, Scala, Swift, Dart, Bash, SQL, Lua, R, Dockerfile, Docker Compose, Markdown, JSON, HTML, CSS, XML.
A maioria dos parsers é ciente de frameworks onde importa — estereótipos Spring e rotas HTTP para Java/Kotlin, FastAPI/Pydantic
para Python, ASP.NET para C#, Rails para Ruby, Laravel/Symfony para PHP, widgets React/SwiftUI/Flutter, etc. Veja
server/parser/ para os detalhes de extração por linguagem.
Configuração
Pré-requisitos: Python 3.12+, Docker, token GitHub
# Install dependencies
uv sync
# Copy environment file, then edit .env to set GITHUB_TOKEN
# (a fine-grained PAT with Contents: read on the target repos is sufficient)
cp .env.example .env
# Optional — only if you want curated/static services (see below for the alternative):
# copy the services config, then list the repositories you want indexed
cp config.example.yaml config.yaml
Configure quais repositórios indexar em config.yaml:
services:
- name: my-service
github_repo: owner/repo
github_ref: main # optional, defaults to "main" — branch, tag, or commit SHA
root: src/main/java # optional — limit indexing to this subdirectory (useful for monorepos)
exclude: # optional — skip matching paths
- "**/vendor/**"
- "**/node_modules/**"
O indexador descobre e indexa automaticamente todos os arquivos com extensões reconhecidas. Use root para escopar um serviço a um
subdiretório dentro de um repositório compartilhado, e exclude para pular caminhos que você não quer indexar (testes, artefatos de build, código
gerado, etc.).
Escalando além de um punhado de repositórios: config.yaml é uma lista estática e curada — ótima para um pequeno número de serviços,
mas indexar centenas de repositórios dessa forma significa centenas de entradas mantidas manualmente. Como alternativa (ou complemento),
POST /reindex aceita uma definição de repositório inline e o registra em tempo real, sem necessidade de entrada em config.yaml — veja
examples/github-actions/reindex-on-merge.yml para um fluxo de trabalho pronto
que auto-registra um repositório e o reindexa a cada merge. Detalhes na seção API HTTP abaixo. Se um
nome colidir entre os dois, a entrada em config.yaml sempre vence.
Você não precisa de um config.yaml para executar dessa forma — um arquivo ausente é tratado como zero serviços configurados, não como um
erro. docker-compose.yaml reflete isso: por padrão, ele não monta config.yaml, então make docker-up /
make docker-up-jina funcionam prontamente para configurações somente com registro ad-hoc. Se você também quiser serviços curados,
copie config.example.yaml para config.yaml (acima) e use os alvos -with-config em vez disso, que adicionam
docker-compose.config-yaml.yml por cima para adicionar a montagem: make docker-up-with-config / make docker-up-jina-with-config
(ou docker compose -f docker-compose.yaml -f docker-compose.config-yaml.yml up -d diretamente). Não edite manualmente a
linha de volume em docker-compose.yaml — montar por bind um config.yaml que não existe no host silenciosamente
cria um diretório vazio lá em vez de deixar o caminho ausente, o que quebra o servidor (exibido como um erro claro
se isso acontecer: CONFIG_PATH (...) is a directory, not a file).
Um único GITHUB_TOKEN lê cada repositório que você indexa dessa forma. Para um punhado de entradas config.yaml, um
PAT de escopo fino limitado a esses repositórios é suficiente, mas para auto-registro em toda a organização — onde qualquer repositório pode se integrar
apenas adicionando o workflow — um PAT precisaria ter sua lista de acesso a repositórios atualizada fora de banda toda vez que um novo repositório
começasse a usá-lo. Um GitHub App instalado em toda a organização (todos os repositórios, Contents: read) evita isso: novos repositórios são cobertos
automaticamente, sem manutenção de token por integração.
Execução
Há duas maneiras de executar, dependendo se você quer que os embeddings venham de um contêiner local ou de um provedor hospedado. Escolha uma:
Caminho A — Jina local via TEI (padrão, sem chave de API necessária):
make docker-up-jina
# or: docker-compose --profile jina up
Caminho B — provedor hospedado (Voyage / OpenAI) ou Ollama local:
# 1. In .env, set EMBEDDINGS_PROVIDER=voyage|openai|ollama and the relevant API key.
# 2. Then start without the jina profile:
make docker-up
# or: docker-compose up
Usando config.yaml para serviços curados? Use a variante -with-config de qualquer alvo acima aplicável
(make docker-up-with-config / make docker-up-jina-with-config) — veja a seção Configuração.
⚠ O
EMBEDDINGS_PROVIDERpadrão éjina. Se você iniciar sem--profile jinamas deixar o provedor no padrão, o semcode iniciará (Jina érequired: falseno compose), mas a primeira chamada de embedding falhará com um erro de conexão — não há fallback automático.
Serviços iniciados com health checks e volumes persistentes:
| Serviço | Perfil | Porta | Volume | Propósito |
|---|---|---|---|---|
| Qdrant | sempre | 6333 (HTTP), 6334 (gRPC) | qdrant_data | Banco de vetores |
| Jina Embeddings (TEI) | jina | 8087 | embeddings_cache | Servidor de modelo de embedding |
| semcode MCP | sempre | 8090 | monta ./config.yaml somente leitura com -with-config | Servidor MCP + HTTP |
O servidor MCP inicia com coleções vazias — acione uma indexação inicial chamando a ferramenta MCP reindex
ou POST /reindex (veja abaixo).
Conectando clientes de IA
Uma vez que o servidor esteja em execução, aponte seu cliente de IA para http://localhost:8090/mcp.
Claude Code (CLI)
claude mcp add --transport http semcode http://localhost:8090/mcp
Outros clientes MCP (Claude Desktop, Cursor, etc.) — adicione uma entrada à configuração MCP do cliente:
{
"mcpServers": {
"semcode": {
"transport": "http",
"url": "http://localhost:8090/mcp"
}
}
}
Conectando via stdio
Em vez de apontar para um servidor HTTP em execução, o cliente pode iniciar o processo do servidor ele mesmo e
conversar com ele via stdin/stdout. Isso ainda requer que o Qdrant esteja acessível (por exemplo, docker-compose up qdrant)
e um ambiente Python local com dependências instaladas (uv sync).
Claude Code (CLI)
claude mcp add semcode --transport stdio --env MCP_TRANSPORT=stdio -- uv run --directory /path/to/semcode python -m server.main
Outros clientes MCP — adicione uma entrada à configuração MCP do cliente:
{
"mcpServers": {
"semcode": {
"command": "uv",
"args": ["run", "--directory", "/path/to/semcode", "python", "-m", "server.main"],
"env": {
"MCP_TRANSPORT": "stdio"
}
}
}
}
GITHUB_TOKEN, QDRANT_URL e variáveis do provedor de embedding ainda são lidas de .env no
diretório do projeto — uv run o detecta automaticamente.
Conectando via SSE
SSE é o transporte HTTP MCP legado, substituído por
streamable-http. Use-o apenas para clientes que ainda não suportamstreamable-http— novas configurações devem usar a configuraçãostreamable-httpacima.
Defina MCP_TRANSPORT=sse em .env (ou no ambiente) e inicie o servidor da mesma forma que
streamable-http (make docker-up / make docker-up-jina, ou uv run python -m server.main
localmente). O servidor expõe um endpoint SSE em http://localhost:8090/sse.
Claude Code (CLI)
claude mcp add --transport sse semcode http://localhost:8090/sse
Outros clientes MCP — adicione uma entrada à configuração MCP do cliente:
{
"mcpServers": {
"semcode": {
"transport": "sse",
"url": "http://localhost:8090/sse"
}
}
}
Indexação
O pipeline de indexação é orientado a símbolos: cada função, classe, método ou componente se torna seu próprio chunk com um embedding vetorial e um payload rico.
- Descoberta — lista todos os arquivos no repositório em
github_ref, aplicando filtrosrooteexclude - Detecção de mudanças — compara o SHA do blob Git do arquivo com o último valor indexado; arquivos inalterados são ignorados
- Análise — Tree-sitter percorre a AST e emite objetos
CodeSymbolpor linguagem - Texto de embedding denso — rótulo de linguagem, tipo de símbolo, classe pai, pacote, extras de framework (estereótipo Spring,
rota HTTP, Lombok, React memo), docstring, assinatura e código-fonte (fonte truncada em
EMBEDDING_MAX_CHARS, um padrão ciente do provedor — veja docs/configuration.md) - Texto de embedding esparso (BM25) — assinatura, docstring e código-fonte. Identificadores de código são divididos em subpalavras (
camelCase, snake_case) antes da tokenização, então
getUserByIdindexa comoget,user,by,idalém do token completo - Loteamento — provedor denso em lotes de 32 (Jina/TEI, Ollama) ou 128 (Voyage, OpenAI); BM25 roda em processo
- Upsert — ambos os vetores armazenados sob um único ponto no Qdrant, chaveado por um UUID determinístico (por serviço / arquivo / símbolo / linha)
- Limpeza — entradas para arquivos que não estão mais no repositório (ou que agora analisam para zero símbolos) são excluídas
A indexação do histórico Git é um pipeline separado e opcional que incorpora mensagens de commit e caminhos de arquivos
alterados na coleção git_commits. Diffs unificados completos são armazenados no payload e recuperáveis
via a ferramenta get_commit. O número de commits por serviço é limitado por GIT_HISTORY_MAX_COMMITS
(padrão 500).
Testes
uv sync --group dev
uv run pytest
Testes ficam em tests/:
tests/parser/test_*.py— um arquivo por linguagem; captura o comportamento do parser contra fixtures canônicas emtests/fixtures/<language>/tests/test_pipeline.py,tests/test_store.py,tests/test_git_history.py— testes de integração para o pipeline de indexação e o armazenamento Qdranttests/test_reindex_route.py— testes de rotas HTTP
Ferramentas MCP
| Ferramenta | Descrição |
|---|---|
search_code | Busca híbrida (densa + BM25) por consulta, com filtros opcionais para idioma, serviço e tipo de símbolo |
find_symbol | Busca um símbolo pelo nome — correspondência exata ou correspondência de token sem diferenciar maiúsculas/minúsculas quando exact=false |
find_usages | Encontra código que referencia um nome de símbolo específico (busca semântica, depois exclui a própria definição) |
get_code_context | Busca o código-fonte completo de um arquivo — ou um símbolo específico dentro dele — diretamente do GitHub |
reindex | Dispara a indexação de código de um ou de todos os serviços (incremental por padrão; force para re-incorporar) |
index_history | Indexa o histórico de commits do git; busca automaticamente os diffs de commits que não os possuem |
search_commits | Busca no histórico de commits do git com linguagem natural |
get_commit | Obtém detalhes completos de um commit específico, incluindo arquivos alterados e diffs |
list_indexed_services | Lista serviços indexados com contagens de chunks e arquivos, idiomas e horário da última indexação |
index_stats | Mostra estatísticas da coleção Qdrant e serviços configurados |
find_symbol(exact=false) corresponde a um índice de texto completo sobre os tokens camelCase/snake_case do nome do símbolo, portanto
order ou ord encontra placeOrderRequest em ~2 ms, independentemente do tamanho da coleção. Fragmentos no meio do token (rder) ainda
correspondem, mas recorrem a uma varredura no lado do cliente que é linear em relação ao tamanho da coleção. Coleções indexadas antes deste campo
existir usam esse mesmo fallback até serem reindexadas — e como a detecção de alterações ignora arquivos inalterados, preencher o
campo exige uma reindexação forçada (POST /reindex {"force": true}), que re-incorpora cada símbolo. Consulte
docs/retrieval-rrf.md.
Prompts MCP
| Prompt | Argumentos | Descrição |
|---|---|---|
service_overview | service | Orienta o cliente na produção de uma visão geral arquitetural de um serviço: pontos de entrada HTTP, tipos de domínio e convenções notáveis de framework |
system_design_overview | (nenhum) | Orienta o cliente na produção de uma visão geral completa do design do sistema: inventário de serviços, topologia de comunicação, armazenamentos de dados compartilhados e preocupações transversais — inclui diagramas Mermaid |
API HTTP
Além das ferramentas MCP, o servidor expõe dois endpoints HTTP para acionar a indexação a partir de CI/CD ou agendadores externos:
| Endpoint | Corpo | Descrição |
|---|---|---|
POST /reindex | {"service": "<name>"?, "force": <bool>?, "github_repo": "<owner/repo>"?, "github_ref": "<ref>"?, "root": "<path>"?, "exclude": [<glob>, ...]?} | Reindexa um ou todos os serviços — retorna NDJSON |
POST /reindex-history | {"service": "<name>"?, "force": <bool>?} | Indexa o histórico de commits do git — retorna NDJSON |
Todos os corpos são opcionais — omita service para agir em todos os serviços, omita force para indexação incremental.
Ambos os endpoints transmitem JSON delimitado por nova linha (um frame por linha) para que você possa consumir o progresso
em tempo real a partir de pipelines de CI/CD ou qualquer outro cliente.
Registrando um repositório sem config.yaml: se o corpo de POST /reindex incluir github_repo, o
nome service é registrado com essa definição de repositório (persistido, portanto sobrevive a reinicializações e se comporta
como um serviço config.yaml a partir de então) antes de a indexação ser executada — service é obrigatório neste caso.
github_ref tem como padrão main; root/exclude espelham os mesmos campos em config.yaml. Um nome service
já definido em config.yaml sempre vence sobre um registrado dessa forma. Não há
autenticação neste endpoint — igual ao restante de /reindex — portanto coloque-o atrás do seu próprio limite de rede
antes de expô-lo. Consulte
examples/github-actions/reindex-on-merge.yml para um
fluxo de trabalho pronto para uso.
Formatos de frame:
// in-flight progress
{"type": "progress", "phase": "discovery|upserting|cleanup", "current": 12, "total": 200, "percentage": 6.0, "service": "my-service"}
// final summary (one per request)
{"type": "done", "result": {"files": 42, "chunks": 318, "skipped": 5}}
// emitted instead of "done" on failure
{"type": "error", "message": "..."}
Para /reindex, os frames upserting carregam current como uma contagem monotonicamente crescente de arquivos
resolvidos — indexados, ignorados como inalterados ou descartados por uma falha de busca/análise — portanto sempre termina em
total. Os arquivos são indexados simultaneamente, então os frames são emitidos por lote, e não por arquivo.
Para /reindex-history, o valor phase é discovery|embedding|upserting e o resultado done é
{"new": int, "skipped": int, "diff_updated": int}.
Variáveis de ambiente
| Variável | Padrão | Descrição |
|---|---|---|
GITHUB_TOKEN | (obrigatório) | Token do GitHub com acesso de leitura ao repositório |
QDRANT_URL | http://localhost:6333 | URL de conexão do Qdrant |
QDRANT_COLLECTION | code_symbols | Nome da coleção para vetores de símbolos de código |
QDRANT_COMMITS_COLLECTION | git_commits | Nome da coleção para vetores de mensagens de commit |
EMBEDDINGS_PROVIDER | jina | Um de jina, jina-api, voyage, openai, ollama — consulte Provedores de incorporação abaixo |
GIT_HISTORY_MAX_COMMITS | 500 | Máximo de commits indexados por serviço |
CODE_CONTEXT_CACHE_SIZE | 128 | Arquivos armazenados em cache na memória para get_code_context (chaveados por blob SHA); 0 desativa |
CODE_CONTEXT_CACHE_TTL | 900 | Segundos durante os quais o conteúdo de um arquivo em cache permanece válido |
MCP_TRANSPORT | streamable-http | Um de streamable-http, sse, stdio |
MCP_HOST / MCP_PORT | 127.0.0.1 / 8090 | Endereço de vinculação do servidor |
MCP_STATELESS_HTTP | true | Serve streamable-http sem rastreamento de sessão (sem necessidade de sessões fixas); false restaura sessões MCP por cliente. Somente streamable-http |
CONFIG_PATH | ./config.yaml | Caminho para o arquivo de configuração de serviços |
Provedores de incorporação
O backend de incorporação é selecionável via EMBEDDINGS_PROVIDER. O padrão é jina para que as implantações
existentes continuem funcionando sem alterações. Cada provedor deriva suas próprias dimensões de vetor do
modelo configurado — não é necessário definir dimensões manualmente, a menos que você queira substituir.
| Variável | Padrão | Aplica-se a | Descrição |
|---|---|---|---|
JINA_URL | http://localhost:8087 | jina | URL base do TEI |
JINA_MODEL | jinaai/jina-embeddings-v2-base-code | jina | Apenas informativo — a flag --model-id do contêiner TEI é o que realmente carrega. Edite docker-compose.yaml para alterar modelos. |
JINA_DIMENSIONS | 768 | jina | Dimensões de vetor do modelo TEI |
JINA_API_KEY | (obrigatório se provider=jina-api) | jina-api | Chave da API Jina AI (endpoint hospedado em api.jina.ai) |
JINA_API_MODEL | jina-embeddings-v2-base-code | jina-api | Modelo Jina hospedado — também suporta jina-code-embeddings-0.5b, jina-code-embeddings-1.5b |
JINA_API_DIMENSIONS | (nativo) | jina-api | Substituição opcional de Matryoshka (modelos de incorporação de código suportam redução); obrigatório para modelos sem um padrão nativo |
VOYAGE_API_KEY | (obrigatório se provider=voyage) | voyage | Chave da API Voyage AI |
VOYAGE_MODEL | voyage-code-3 | voyage | Modelo de incorporação Voyage |
VOYAGE_DIMENSIONS | (nativo) | voyage | Substituição opcional — Voyage code-3 suporta 256 / 512 / 1024 / 2048 |
OPENAI_API_KEY | (obrigatório se provider=openai) | openai | Chave da API OpenAI |
OPENAI_EMBEDDING_MODEL | text-embedding-3-large | openai | Modelo de incorporação OpenAI |
OPENAI_DIMENSIONS | (nativo) | openai | Substituição opcional (modelos text-embedding-3-* suportam redução) |
OLLAMA_URL | http://localhost:11434 | ollama | URL base do Ollama |
OLLAMA_MODEL | nomic-embed-text | ollama | Modelo de incorporação Ollama |
OLLAMA_DIMENSIONS | (nativo) | ollama | Obrigatório se estiver usando um modelo que não está na tabela de dimensões integrada |
voyage-code-3 supera jinaai/jina-embeddings-v2-base-code na maioria dos benchmarks de recuperação de código,
portanto mudar para Voyage também é uma alavanca de qualidade, não apenas de flexibilidade.
Trocando provedores em um índice existente: se o tamanho do vetor do novo provedor for diferente
da coleção Qdrant existente, o servidor falha rapidamente na inicialização com um erro claro apontando para a
coleção problemática. Para trocar, remova ambas as coleções (code_symbols e git_commits) via
interface ou API do Qdrant e depois reindexe. Não há migração automática.
Configuração somente hospedada (sem contêiner TEI local): defina EMBEDDINGS_PROVIDER e a chave de API relevante
em .env, depois inicie sem o perfil jina (docker-compose up / make docker-up). O
contêiner jina-embeddings não será iniciado.
Coleções Qdrant
code_symbols — um ponto por símbolo analisado, carregando dois vetores nomeados:
text-dense— distância de cosseno, HNSW (m=16,ef_construct=128), tamanho determinado pelo provedor de incorporaçãotext-sparse— BM25 sobre tokens de subpalavras de identificadores de código, índice esparso em memória
search_code consulta ambos via uma chamada query_points do Qdrant com FusionQuery(fusion=RRF). Campos de payload
indexados (language, service, symbol_type, chunk_tier, parent_name, file_path) são
utilizáveis como filtros. search_code e find_symbol expõem chunk_tier ("method" ou "class")
diretamente, então uma consulta pode ser limitada apenas a classes ou apenas a métodos. O payload completo também inclui
signature, docstring, annotations, package,
start_line, end_line, file_hash, indexed_at, e extras específicos da linguagem (http_method,
http_route, spring_stereotype, lombok_annotations, is_async, uses_memo, …).
git_commits — um vetor somente denso por commit (cosseno, HNSW m=16 / ef_construct=128).
O payload inclui sha, service, message, author_name, author_email, committed_at,
indexed_at, has_diff, diff_truncated, e files (matriz de arquivos alterados com filename,
status, additions, deletions, patch). sha, service, author_name, e has_diff são
campos de payload indexados.
Versionamento e lançamentos
A versão vive em um lugar: version em pyproject.toml. server/main.py a lê de volta através de
importlib.metadata.version("semcode") e a reporta como o serverInfo.version do MCP, então um cliente sempre vê
a versão do pacote que está realmente executando.
Nunca edite essa versão manualmente. Lançamentos são automatizados com
release-please (.github/workflows/release.yml):
- Commits chegam em
mainusando Conventional Commits. - release-please abre (ou atualiza) um PR
chore(main): release X.Y.Zque incrementapyproject.tomle escreveCHANGELOG.mda partir dessas mensagens de commit. - Mesclar esse PR marca o commit com a tag
vX.Y.Ze cria um GitHub Release. Nada é lançado até que um humano o mescle.
Um lançamento atualmente produz um GitHub Release com o código-fonte e o changelog gerado. Nada é publicado no
PyPI, e nenhuma imagem de contêiner é enviada — semcode ainda é construído localmente a partir do Dockerfile.
Tipos de commit e incrementos de versão
| Prefixo do commit | Incremento | Seção do changelog |
|---|---|---|
feat!: / BREAKING CHANGE: | major | ⚠ Mudanças de Quebra |
feat: | minor | Recursos |
fix: | patch | Correções de Bugs |
perf: | patch | Melhorias de Desempenho |
deps: | patch | Dependências |
refactor: | patch | Refatoração de Código |
docs: test: style: chore: ci: build: | patch | oculto |
Dependabot está configurado (.github/dependabot.yml) para usar o prefixo deps, então uma semana de atualizações de dependências
ainda produz um lançamento de patch que marca qual conjunto de dependências foi testado junto.
Major é reservado para mudanças de quebra nas assinaturas de ferramentas do MCP ou no esquema de configuração — as superfícies das quais clientes e implantações dependem.
Notas
- Como as mensagens de commit em
mainimpulsionam tudo, títulos de squash-merge devem ser Conventional Commits bem formados. O repositório deve ter "Padrão para título do PR em squash merges" habilitado. - O PR de lançamento é criado com
GITHUB_TOKEN, que por design não aciona outros fluxos de trabalho — CI não é executado novamente no próprio PR de lançamento. Os commits que ele lança já foram testados emmain.
Estrutura do projeto
server/
├── main.py # MCP server entry point + lifespan
├── config.py # Settings and service configuration
├── state.py # Shared store singletons
├── parser/ # Tree-sitter parsers (Go, Java, Python, TypeScript, Rust, C#, C, C++, Ruby, PHP, Kotlin, Scala, Swift, Dart, Bash, SQL, Lua, R, Dockerfile, Compose, Markdown, JSON, HTML, CSS, XML)
├── embeddings/ # Pluggable dense providers (Jina/Voyage/OpenAI/Ollama) + BM25 sparse + code identifier tokenizer
├── indexer/ # GitHub fetcher, code indexing pipeline, git history pipeline
├── store/ # Qdrant vector stores (code_symbols hybrid + git_commits dense)
├── tools/ # MCP tool implementations (search, index, history, admin)
├── prompts/ # MCP prompt templates (service_overview, system_design_overview)
└── routes/ # HTTP routes (reindex, reindex-history)